Merge changes Ic8f22359,I3b94dee1

* changes:
  Don't query settings for the notification history state every time we update the footer.
  Add trace sections for comparison of the notification pipelines
diff --git a/Android.bp b/Android.bp
index 838f304..e03f844 100644
--- a/Android.bp
+++ b/Android.bp
@@ -169,7 +169,6 @@
         "framework-statsd.impl",
         "framework-supplementalprocess.impl",
         "framework-tethering.impl",
-        "framework-nearby.impl",
         "framework-uwb.impl",
         "framework-wifi.impl",
         "updatable-media",
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index afad29c..c4795f5 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -364,6 +364,11 @@
      * @hide
      */
     public static final int REASON_SYSTEM_MODULE = 320;
+    /**
+     * Carrier privileged app.
+     * @hide
+     */
+    public static final int REASON_CARRIER_PRIVILEGED_APP = 321;
 
     /** @hide The app requests out-out. */
     public static final int REASON_OPT_OUT_REQUESTED = 1000;
@@ -440,6 +445,7 @@
             REASON_ROLE_DIALER,
             REASON_ROLE_EMERGENCY,
             REASON_SYSTEM_MODULE,
+            REASON_CARRIER_PRIVILEGED_APP,
             REASON_OPT_OUT_REQUESTED,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -749,6 +755,8 @@
                 return "ROLE_EMERGENCY";
             case REASON_SYSTEM_MODULE:
                 return "SYSTEM_MODULE";
+            case REASON_CARRIER_PRIVILEGED_APP:
+                return "CARRIER_PRIVILEGED_APP";
             case REASON_OPT_OUT_REQUESTED:
                 return "REASON_OPT_OUT_REQUESTED";
             default:
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 9b1f2d0..13ecd25 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -3,6 +3,7 @@
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.UsageStatsManager.ForcedReasons;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
@@ -216,4 +217,19 @@
     void dumpState(String[] args, PrintWriter pw);
 
     boolean isAppIdleEnabled();
+
+    /**
+     * Returns the duration (in millis) for the window where events occurring will be
+     * considered as broadcast response, starting from the point when an app receives
+     * a broadcast.
+     */
+    long getBroadcastResponseWindowDurationMs();
+
+    /**
+     * Returns the process state threshold that should be used for deciding whether or not an app
+     * is in the background in the context of recording broadcast response stats. Apps whose
+     * process state is higher than this threshold state should be considered to be in background.
+     */
+    @ProcessState
+    int getBroadcastResponseFgThresholdState();
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index b2ae8ee..cea1945 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -357,6 +357,9 @@
     // Putting RESTRICTED_INDEX after NEVER_INDEX to make it easier for proto dumping
     // (ScheduledJobStateChanged and JobStatusDumpProto).
     public static final int RESTRICTED_INDEX = 5;
+    // Putting EXEMPTED_INDEX after RESTRICTED_INDEX to make it easier for proto dumping
+    // (ScheduledJobStateChanged and JobStatusDumpProto).
+    public static final int EXEMPTED_INDEX = 6;
 
     private class ConstantsObserver implements DeviceConfig.OnPropertiesChangedListener,
             EconomyManagerInternal.TareStateChangeListener {
@@ -2492,6 +2495,7 @@
                     shouldForceBatchJob =
                             mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
                                     && job.getEffectiveStandbyBucket() != ACTIVE_INDEX
+                                    && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
                                     && !batchDelayExpired;
                 }
 
@@ -2764,7 +2768,7 @@
                 return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
                         ? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS
                         : Math.min(mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, 5 * MINUTE_IN_MILLIS);
-            } else if (job.getEffectivePriority() == JobInfo.PRIORITY_HIGH) {
+            } else if (job.getEffectivePriority() >= JobInfo.PRIORITY_HIGH) {
                 return mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
             } else {
                 return mConstants.RUNTIME_MIN_GUARANTEE_MS;
@@ -3086,8 +3090,10 @@
             return FREQUENT_INDEX;
         } else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
             return WORKING_INDEX;
-        } else {
+        } else if (bucket > UsageStatsManager.STANDBY_BUCKET_EXEMPTED) {
             return ACTIVE_INDEX;
+        } else {
+            return EXEMPTED_INDEX;
         }
     }
 
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 0eea701..0456a9b 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
@@ -17,6 +17,7 @@
 package com.android.server.job.controllers;
 
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
@@ -844,12 +845,15 @@
      * exemptions.
      */
     public int getEffectiveStandbyBucket() {
+        final int actualBucket = getStandbyBucket();
+        if (actualBucket == EXEMPTED_INDEX) {
+            return actualBucket;
+        }
         if (uidActive || getJob().isExemptedFromAppStandby()) {
             // Treat these cases as if they're in the ACTIVE bucket so that they get throttled
             // like other ACTIVE apps.
             return ACTIVE_INDEX;
         }
-        final int actualBucket = getStandbyBucket();
         if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
                 && mHasMediaBackupExemption) {
             // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index dd5246a..c1728a3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -21,6 +21,7 @@
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -132,6 +133,7 @@
          */
         public long expirationTimeElapsed;
 
+        public long allowedTimePerPeriodMs;
         public long windowSizeMs;
         public int jobCountLimit;
         public int sessionCountLimit;
@@ -213,6 +215,7 @@
         @Override
         public String toString() {
             return "expirationTime=" + expirationTimeElapsed + ", "
+                    + "allowedTimePerPeriodMs=" + allowedTimePerPeriodMs + ", "
                     + "windowSizeMs=" + windowSizeMs + ", "
                     + "jobCountLimit=" + jobCountLimit + ", "
                     + "sessionCountLimit=" + sessionCountLimit + ", "
@@ -236,6 +239,7 @@
             if (obj instanceof ExecutionStats) {
                 ExecutionStats other = (ExecutionStats) obj;
                 return this.expirationTimeElapsed == other.expirationTimeElapsed
+                        && this.allowedTimePerPeriodMs == other.allowedTimePerPeriodMs
                         && this.windowSizeMs == other.windowSizeMs
                         && this.jobCountLimit == other.jobCountLimit
                         && this.sessionCountLimit == other.sessionCountLimit
@@ -261,6 +265,7 @@
         public int hashCode() {
             int result = 0;
             result = 31 * result + hashLong(expirationTimeElapsed);
+            result = 31 * result + hashLong(allowedTimePerPeriodMs);
             result = 31 * result + hashLong(windowSizeMs);
             result = 31 * result + hashLong(jobCountLimit);
             result = 31 * result + hashLong(sessionCountLimit);
@@ -350,7 +355,15 @@
     private boolean mIsEnabled;
 
     /** How much time each app will have to run jobs within their standby bucket window. */
-    private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
+    private final long[] mAllowedTimePerPeriodMs = new long[]{
+            QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+            QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+            QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+            QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS,
+            0, // NEVER
+            QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+            QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS
+    };
 
     /**
      * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
@@ -365,12 +378,6 @@
     private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS;
 
     /**
-     * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
-     * when an app will have enough quota to transition from out-of-quota to in-quota.
-     */
-    private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
-
-    /**
      * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
      * app will have enough quota to transition from out-of-quota to in-quota.
      */
@@ -450,7 +457,8 @@
             QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
             QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS,
             0, // NEVER
-            QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS
+            QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS,
+            QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS
     };
 
     /** The maximum period any bucket can have. */
@@ -469,7 +477,8 @@
             QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
             QcConstants.DEFAULT_MAX_JOB_COUNT_RARE,
             0, // NEVER
-            QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED
+            QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED,
+            QcConstants.DEFAULT_MAX_JOB_COUNT_EXEMPTED
     };
 
     /**
@@ -487,6 +496,7 @@
             QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE,
             0, // NEVER
             QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED,
+            QcConstants.DEFAULT_MAX_SESSION_COUNT_EXEMPTED,
     };
 
     /**
@@ -506,7 +516,8 @@
             QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS,
             QcConstants.DEFAULT_EJ_LIMIT_RARE_MS,
             0, // NEVER
-            QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS
+            QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS,
+            QcConstants.DEFAULT_EJ_LIMIT_EXEMPTED_MS
     };
 
     private long mEjLimitAdditionInstallerMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS;
@@ -823,6 +834,11 @@
         if (mService.isBatteryCharging()) {
             return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
         }
+        if (jobStatus.getEffectiveStandbyBucket() == EXEMPTED_INDEX) {
+            return Math.max(mEJLimitsMs[EXEMPTED_INDEX] / 2,
+                    getTimeUntilEJQuotaConsumedLocked(
+                            jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()));
+        }
         if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) {
             return Math.max(mEJLimitsMs[ACTIVE_INDEX] / 2,
                     getTimeUntilEJQuotaConsumedLocked(
@@ -929,9 +945,11 @@
 
         final long minSurplus;
         if (priority <= JobInfo.PRIORITY_MIN) {
-            minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityMin);
+            minSurplus = (long)
+                    (mAllowedTimePerPeriodMs[standbyBucket] * mAllowedTimeSurplusPriorityMin);
         } else if (priority <= JobInfo.PRIORITY_LOW) {
-            minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityLow);
+            minSurplus = (long)
+                    (mAllowedTimePerPeriodMs[standbyBucket] * mAllowedTimeSurplusPriorityLow);
         } else {
             minSurplus = 0;
         }
@@ -989,7 +1007,7 @@
     }
 
     private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) {
-        return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs,
+        return Math.min(stats.allowedTimePerPeriodMs - stats.executionTimeInWindowMs,
                 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs);
     }
 
@@ -1068,15 +1086,15 @@
         if (sessions == null || sessions.size() == 0) {
             // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
             // essentially run until they reach the maximum limit.
-            if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
+            if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
                 return mMaxExecutionTimeMs;
             }
-            return mAllowedTimePerPeriodMs;
+            return mAllowedTimePerPeriodMs[standbyBucket];
         }
 
         final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
         final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
-        final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(jobPriority);
+        final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(standbyBucket, jobPriority);
         final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs;
         final long maxExecutionTimeRemainingMs =
                 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;
@@ -1087,7 +1105,7 @@
 
         // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
         // essentially run until they reach the maximum limit.
-        if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
+        if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
             return calculateTimeUntilQuotaConsumedLocked(
                     sessions, startMaxElapsed, maxExecutionTimeRemainingMs);
         }
@@ -1103,14 +1121,19 @@
                         sessions, startWindowElapsed, allowedTimeRemainingMs));
     }
 
-    private long getAllowedTimePerPeriodMs(@JobInfo.Priority int jobPriority) {
+    private long getAllowedTimePerPeriodMs(int standbyBucket, @JobInfo.Priority int jobPriority) {
+        return getAllowedTimePerPeriodMs(mAllowedTimePerPeriodMs[standbyBucket], jobPriority);
+    }
+
+    private long getAllowedTimePerPeriodMs(long initialAllowedTime,
+            @JobInfo.Priority int jobPriority) {
         if (jobPriority <= JobInfo.PRIORITY_MIN) {
-            return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityMin));
+            return (long) (initialAllowedTime * (1 - mAllowedTimeSurplusPriorityMin));
         }
         if (jobPriority <= JobInfo.PRIORITY_LOW) {
-            return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityLow));
+            return (long) (initialAllowedTime * (1 - mAllowedTimeSurplusPriorityLow));
         }
-        return mAllowedTimePerPeriodMs;
+        return initialAllowedTime;
     }
 
     /**
@@ -1237,16 +1260,19 @@
             appStats[standbyBucket] = stats;
         }
         if (refreshStatsIfOld) {
+            final long bucketAllowedTimeMs = mAllowedTimePerPeriodMs[standbyBucket];
             final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
             final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
             final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
             Timer timer = mPkgTimers.get(userId, packageName);
             if ((timer != null && timer.isActive())
                     || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
+                    || stats.allowedTimePerPeriodMs != bucketAllowedTimeMs
                     || stats.windowSizeMs != bucketWindowSizeMs
                     || stats.jobCountLimit != jobCountLimit
                     || stats.sessionCountLimit != sessionCountLimit) {
                 // The stats are no longer valid.
+                stats.allowedTimePerPeriodMs = bucketAllowedTimeMs;
                 stats.windowSizeMs = bucketWindowSizeMs;
                 stats.jobCountLimit = jobCountLimit;
                 stats.sessionCountLimit = sessionCountLimit;
@@ -1272,8 +1298,11 @@
         } else {
             stats.inQuotaTimeElapsed = 0;
         }
-        final long allowedTimeMinMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_MIN);
-        final long allowedTimeLowMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_LOW);
+        final long allowedTimeMinMs =
+                getAllowedTimePerPeriodMs(stats.allowedTimePerPeriodMs, JobInfo.PRIORITY_MIN);
+        final long allowedTimeLowMs =
+                getAllowedTimePerPeriodMs(stats.allowedTimePerPeriodMs, JobInfo.PRIORITY_LOW);
+        final long allowedTimeIntoQuotaMs = stats.allowedTimePerPeriodMs - mQuotaBufferMs;
 
         Timer timer = mPkgTimers.get(userId, packageName);
         final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -1287,9 +1316,9 @@
             // If the timer is active, the value will be stale at the next method call, so
             // invalidate now.
             stats.expirationTimeElapsed = nowElapsed;
-            if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+            if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) {
                 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                        nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
+                        nowElapsed - allowedTimeIntoQuotaMs + stats.windowSizeMs);
             }
             if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
                 stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
@@ -1346,9 +1375,9 @@
 
                 stats.executionTimeInWindowMs += session.endTimeElapsed - start;
                 stats.bgJobCountInWindow += session.bgJobCount;
-                if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+                if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) {
                     stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                            start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
+                            start + stats.executionTimeInWindowMs - allowedTimeIntoQuotaMs
                                     + stats.windowSizeMs);
                 }
                 if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
@@ -1697,7 +1726,7 @@
                 if (js.setQuotaConstraintSatisfied(nowElapsed, true)) {
                     changedJobs.add(js);
                 }
-            } else if (realStandbyBucket != ACTIVE_INDEX
+            } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
                     && realStandbyBucket == js.getEffectiveStandbyBucket()
                     && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) {
                 // An app in the ACTIVE bucket may be out of quota while the job could be in quota
@@ -1842,7 +1871,8 @@
             }
         }
         final boolean inRegularQuota =
-                stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority)
+                stats.executionTimeInWindowMs
+                        < getAllowedTimePerPeriodMs(standbyBucket, minPriority)
                         && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
                         && isUnderJobCountQuota
                         && isUnderTimingSessionCountQuota;
@@ -2921,9 +2951,29 @@
         /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
         private static final String QC_CONSTANT_PREFIX = "qc_";
 
+        /**
+         * Previously used keys:
+         *   * allowed_time_per_period_ms -- No longer used after splitting by bucket
+         */
+
         @VisibleForTesting
-        static final String KEY_ALLOWED_TIME_PER_PERIOD_MS =
-                QC_CONSTANT_PREFIX + "allowed_time_per_period_ms";
+        static final String KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+                QC_CONSTANT_PREFIX + "allowed_time_per_period_exempted_ms";
+        @VisibleForTesting
+        static final String KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+                QC_CONSTANT_PREFIX + "allowed_time_per_period_active_ms";
+        @VisibleForTesting
+        static final String KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS =
+                QC_CONSTANT_PREFIX + "allowed_time_per_period_working_ms";
+        @VisibleForTesting
+        static final String KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS =
+                QC_CONSTANT_PREFIX + "allowed_time_per_period_frequent_ms";
+        @VisibleForTesting
+        static final String KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS =
+                QC_CONSTANT_PREFIX + "allowed_time_per_period_rare_ms";
+        @VisibleForTesting
+        static final String KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
+                QC_CONSTANT_PREFIX + "allowed_time_per_period_restricted_ms";
         @VisibleForTesting
         static final String KEY_IN_QUOTA_BUFFER_MS =
                 QC_CONSTANT_PREFIX + "in_quota_buffer_ms";
@@ -2934,6 +2984,9 @@
         static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN =
                 QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_min";
         @VisibleForTesting
+        static final String KEY_WINDOW_SIZE_EXEMPTED_MS =
+                QC_CONSTANT_PREFIX + "window_size_exempted_ms";
+        @VisibleForTesting
         static final String KEY_WINDOW_SIZE_ACTIVE_MS =
                 QC_CONSTANT_PREFIX + "window_size_active_ms";
         @VisibleForTesting
@@ -2952,6 +3005,9 @@
         static final String KEY_MAX_EXECUTION_TIME_MS =
                 QC_CONSTANT_PREFIX + "max_execution_time_ms";
         @VisibleForTesting
+        static final String KEY_MAX_JOB_COUNT_EXEMPTED =
+                QC_CONSTANT_PREFIX + "max_job_count_exempted";
+        @VisibleForTesting
         static final String KEY_MAX_JOB_COUNT_ACTIVE =
                 QC_CONSTANT_PREFIX + "max_job_count_active";
         @VisibleForTesting
@@ -2973,6 +3029,9 @@
         static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
                 QC_CONSTANT_PREFIX + "max_job_count_per_rate_limiting_window";
         @VisibleForTesting
+        static final String KEY_MAX_SESSION_COUNT_EXEMPTED =
+                QC_CONSTANT_PREFIX + "max_session_count_exempted";
+        @VisibleForTesting
         static final String KEY_MAX_SESSION_COUNT_ACTIVE =
                 QC_CONSTANT_PREFIX + "max_session_count_active";
         @VisibleForTesting
@@ -2997,6 +3056,9 @@
         static final String KEY_MIN_QUOTA_CHECK_DELAY_MS =
                 QC_CONSTANT_PREFIX + "min_quota_check_delay_ms";
         @VisibleForTesting
+        static final String KEY_EJ_LIMIT_EXEMPTED_MS =
+                QC_CONSTANT_PREFIX + "ej_limit_exempted_ms";
+        @VisibleForTesting
         static final String KEY_EJ_LIMIT_ACTIVE_MS =
                 QC_CONSTANT_PREFIX + "ej_limit_active_ms";
         @VisibleForTesting
@@ -3039,14 +3101,26 @@
         static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS =
                 QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms";
 
-        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
+        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+                10 * 60 * 1000L; // 10 minutes
+        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+                10 * 60 * 1000L; // 10 minutes
+        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS =
+                10 * 60 * 1000L; // 10 minutes
+        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS =
+                10 * 60 * 1000L; // 10 minutes
+        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS =
+                10 * 60 * 1000L; // 10 minutes
+        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
                 10 * 60 * 1000L; // 10 minutes
         private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
                 30 * 1000L; // 30 seconds
         private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW = .25f;
         private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = .5f;
+        private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS =
+                DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time
         private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
-                DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
+                DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time
         private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
                 2 * 60 * 60 * 1000L; // 2 hours
         private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
@@ -3060,8 +3134,9 @@
         private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
                 MINUTE_IN_MILLIS;
         private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
-        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE =
+        private static final int DEFAULT_MAX_JOB_COUNT_EXEMPTED =
                 75; // 75/window = 450/hr = 1/session
+        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED;
         private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
                 (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
         private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
@@ -3069,8 +3144,10 @@
         private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
                 (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
         private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10;
-        private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
+        private static final int DEFAULT_MAX_SESSION_COUNT_EXEMPTED =
                 75; // 450/hr
+        private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
+                DEFAULT_MAX_SESSION_COUNT_EXEMPTED;
         private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
                 10; // 5/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT =
@@ -3081,6 +3158,7 @@
         private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
         private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
         private static final long DEFAULT_MIN_QUOTA_CHECK_DELAY_MS = MINUTE_IN_MILLIS;
+        private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 45 * MINUTE_IN_MILLIS;
         private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS;
         private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS;
         private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS;
@@ -3096,8 +3174,39 @@
         private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS;
         private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS;
 
-        /** How much time each app will have to run jobs within their standby bucket window. */
-        public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
+        /**
+         * How much time each app in the exempted bucket will have to run jobs within their standby
+         * bucket window.
+         */
+        public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+                DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+        /**
+         * How much time each app in the active bucket will have to run jobs within their standby
+         * bucket window.
+         */
+        public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+        /**
+         * How much time each app in the working set bucket will have to run jobs within their
+         * standby bucket window.
+         */
+        public long ALLOWED_TIME_PER_PERIOD_WORKING_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS;
+        /**
+         * How much time each app in the frequent bucket will have to run jobs within their standby
+         * bucket window.
+         */
+        public long ALLOWED_TIME_PER_PERIOD_FREQUENT_MS =
+                DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS;
+        /**
+         * How much time each app in the rare bucket will have to run jobs within their standby
+         * bucket window.
+         */
+        public long ALLOWED_TIME_PER_PERIOD_RARE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS;
+        /**
+         * How much time each app in the restricted bucket will have to run jobs within their
+         * standby bucket window.
+         */
+        public long ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
+                DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS;
 
         /**
          * How much time the package should have before transitioning from out-of-quota to in-quota.
@@ -3106,7 +3215,7 @@
         public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS;
 
         /**
-         * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by
+         * The percentage of ALLOWED_TIME_PER_PERIOD_*_MS that should not be used by
          * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum
          * surplus of this amount of remaining allowed time before we start running low priority
          * jobs.
@@ -3114,7 +3223,7 @@
         public float ALLOWED_TIME_SURPLUS_PRIORITY_LOW = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW;
 
         /**
-         * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by
+         * The percentage of ALLOWED_TIME_PER_PERIOD_*_MS that should not be used by
          * {@link JobInfo#PRIORITY_MIN low priority} jobs. In other words, there must be a minimum
          * surplus of this amount of remaining allowed time before we start running min priority
          * jobs.
@@ -3123,35 +3232,42 @@
 
         /**
          * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS} within the past
+         * WINDOW_SIZE_MS.
+         */
+        public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_WINDOW_SIZE_EXEMPTED_MS;
+
+        /**
+         * The quota window size of the particular standby bucket. Apps in this standby bucket are
+         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_ACTIVE_MS} within the past
          * WINDOW_SIZE_MS.
          */
         public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS;
 
         /**
          * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_WORKING_MS} within the past
          * WINDOW_SIZE_MS.
          */
         public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS;
 
         /**
          * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_FREQUENT_MS} within the past
          * WINDOW_SIZE_MS.
          */
         public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS;
 
         /**
          * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RARE_MS} within the past
          * WINDOW_SIZE_MS.
          */
         public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS;
 
         /**
          * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS} within the past
          * WINDOW_SIZE_MS.
          */
         public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS;
@@ -3165,6 +3281,12 @@
          * The maximum number of jobs an app can run within this particular standby bucket's
          * window size.
          */
+        public int MAX_JOB_COUNT_EXEMPTED = DEFAULT_MAX_JOB_COUNT_EXEMPTED;
+
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
         public int MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_ACTIVE;
 
         /**
@@ -3204,6 +3326,12 @@
          * The maximum number of {@link TimingSession TimingSessions} an app can run within this
          * particular standby bucket's window size.
          */
+        public int MAX_SESSION_COUNT_EXEMPTED = DEFAULT_MAX_SESSION_COUNT_EXEMPTED;
+
+        /**
+         * The maximum number of {@link TimingSession TimingSessions} an app can run within this
+         * particular standby bucket's window size.
+         */
         public int MAX_SESSION_COUNT_ACTIVE = DEFAULT_MAX_SESSION_COUNT_ACTIVE;
 
         /**
@@ -3232,7 +3360,7 @@
 
         /**
          * The maximum number of {@link TimingSession TimingSessions} that can run within the past
-         * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
+         * {@link #RATE_LIMITING_WINDOW_MS}.
          */
         public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
                 DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -3275,6 +3403,13 @@
          * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring
          * in any rewards or free EJs).
          */
+        public long EJ_LIMIT_EXEMPTED_MS = DEFAULT_EJ_LIMIT_EXEMPTED_MS;
+
+        /**
+         * The total expedited job session limit of the particular standby bucket. Apps in this
+         * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring
+         * in any rewards or free EJs).
+         */
         public long EJ_LIMIT_ACTIVE_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS;
 
         /**
@@ -3358,7 +3493,12 @@
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                 @NonNull String key) {
             switch (key) {
-                case KEY_ALLOWED_TIME_PER_PERIOD_MS:
+                case KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS:
+                case KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS:
+                case KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS:
+                case KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS:
+                case KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS:
+                case KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS:
                 case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW:
                 case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN:
                 case KEY_IN_QUOTA_BUFFER_MS:
@@ -3388,6 +3528,15 @@
                     updateEJLimitConstantsLocked();
                     break;
 
+                case KEY_MAX_JOB_COUNT_EXEMPTED:
+                    MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED);
+                    int newExemptedMaxJobCount =
+                            Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_EXEMPTED);
+                    if (mMaxBucketJobCounts[EXEMPTED_INDEX] != newExemptedMaxJobCount) {
+                        mMaxBucketJobCounts[EXEMPTED_INDEX] = newExemptedMaxJobCount;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
                 case KEY_MAX_JOB_COUNT_ACTIVE:
                     MAX_JOB_COUNT_ACTIVE = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_ACTIVE);
                     int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
@@ -3432,6 +3581,16 @@
                         mShouldReevaluateConstraints = true;
                     }
                     break;
+                case KEY_MAX_SESSION_COUNT_EXEMPTED:
+                    MAX_SESSION_COUNT_EXEMPTED =
+                            properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_EXEMPTED);
+                    int newExemptedMaxSessionCount =
+                            Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_EXEMPTED);
+                    if (mMaxBucketSessionCounts[EXEMPTED_INDEX] != newExemptedMaxSessionCount) {
+                        mMaxBucketSessionCounts[EXEMPTED_INDEX] = newExemptedMaxSessionCount;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
                 case KEY_MAX_SESSION_COUNT_ACTIVE:
                     MAX_SESSION_COUNT_ACTIVE =
                             properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
@@ -3579,15 +3738,34 @@
             // Query the values as an atomic set.
             final DeviceConfig.Properties properties = DeviceConfig.getProperties(
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    KEY_ALLOWED_TIME_PER_PERIOD_MS, KEY_IN_QUOTA_BUFFER_MS,
+                    KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+                    KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+                    KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+                    KEY_IN_QUOTA_BUFFER_MS,
                     KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN,
-                    KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_ACTIVE_MS,
+                    KEY_MAX_EXECUTION_TIME_MS,
+                    KEY_WINDOW_SIZE_EXEMPTED_MS, KEY_WINDOW_SIZE_ACTIVE_MS,
                     KEY_WINDOW_SIZE_WORKING_MS,
                     KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS,
                     KEY_WINDOW_SIZE_RESTRICTED_MS);
-            ALLOWED_TIME_PER_PERIOD_MS =
-                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS,
-                            DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
+            ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+                            DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS);
+            ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+                            DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS);
+            ALLOWED_TIME_PER_PERIOD_WORKING_MS =
+                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+                            DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS);
+            ALLOWED_TIME_PER_PERIOD_FREQUENT_MS =
+                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+                            DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS);
+            ALLOWED_TIME_PER_PERIOD_RARE_MS =
+                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS,
+                            DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS);
+            ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
+                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+                            DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS);
             ALLOWED_TIME_SURPLUS_PRIORITY_LOW =
                     properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW,
                             DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW);
@@ -3598,6 +3776,8 @@
                     DEFAULT_IN_QUOTA_BUFFER_MS);
             MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
                     DEFAULT_MAX_EXECUTION_TIME_MS);
+            WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS,
+                    DEFAULT_WINDOW_SIZE_EXEMPTED_MS);
             WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS,
                     DEFAULT_WINDOW_SIZE_ACTIVE_MS);
             WINDOW_SIZE_WORKING_MS =
@@ -3618,20 +3798,55 @@
                 mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
                 mShouldReevaluateConstraints = true;
             }
-            long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
-                    Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
-            if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
-                mAllowedTimePerPeriodMs = newAllowedTimeMs;
-                mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
+            long minAllowedTimeMs = Long.MAX_VALUE;
+            long newAllowedTimeExemptedMs = Math.min(mMaxExecutionTimeMs,
+                    Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS));
+            minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeExemptedMs);
+            if (mAllowedTimePerPeriodMs[EXEMPTED_INDEX] != newAllowedTimeExemptedMs) {
+                mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = newAllowedTimeExemptedMs;
+                mShouldReevaluateConstraints = true;
+            }
+            long newAllowedTimeActiveMs = Math.min(mMaxExecutionTimeMs,
+                    Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS));
+            minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeActiveMs);
+            if (mAllowedTimePerPeriodMs[ACTIVE_INDEX] != newAllowedTimeActiveMs) {
+                mAllowedTimePerPeriodMs[ACTIVE_INDEX] = newAllowedTimeActiveMs;
+                mShouldReevaluateConstraints = true;
+            }
+            long newAllowedTimeWorkingMs = Math.min(mMaxExecutionTimeMs,
+                    Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_WORKING_MS));
+            minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeWorkingMs);
+            if (mAllowedTimePerPeriodMs[WORKING_INDEX] != newAllowedTimeWorkingMs) {
+                mAllowedTimePerPeriodMs[WORKING_INDEX] = newAllowedTimeWorkingMs;
+                mShouldReevaluateConstraints = true;
+            }
+            long newAllowedTimeFrequentMs = Math.min(mMaxExecutionTimeMs,
+                    Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS));
+            minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeFrequentMs);
+            if (mAllowedTimePerPeriodMs[FREQUENT_INDEX] != newAllowedTimeFrequentMs) {
+                mAllowedTimePerPeriodMs[FREQUENT_INDEX] = newAllowedTimeFrequentMs;
+                mShouldReevaluateConstraints = true;
+            }
+            long newAllowedTimeRareMs = Math.min(mMaxExecutionTimeMs,
+                    Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RARE_MS));
+            minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRareMs);
+            if (mAllowedTimePerPeriodMs[RARE_INDEX] != newAllowedTimeRareMs) {
+                mAllowedTimePerPeriodMs[RARE_INDEX] = newAllowedTimeRareMs;
+                mShouldReevaluateConstraints = true;
+            }
+            long newAllowedTimeRestrictedMs = Math.min(mMaxExecutionTimeMs,
+                    Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS));
+            minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRestrictedMs);
+            if (mAllowedTimePerPeriodMs[RESTRICTED_INDEX] != newAllowedTimeRestrictedMs) {
+                mAllowedTimePerPeriodMs[RESTRICTED_INDEX] = newAllowedTimeRestrictedMs;
                 mShouldReevaluateConstraints = true;
             }
             // Make sure quota buffer is non-negative, not greater than allowed time per period,
             // and no more than 5 minutes.
-            long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs,
+            long newQuotaBufferMs = Math.max(0, Math.min(minAllowedTimeMs,
                     Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS)));
             if (mQuotaBufferMs != newQuotaBufferMs) {
                 mQuotaBufferMs = newQuotaBufferMs;
-                mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
                 mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
                 mShouldReevaluateConstraints = true;
             }
@@ -3652,32 +3867,38 @@
                 mAllowedTimeSurplusPriorityMin = newAllowedTimeSurplusPriorityMin;
                 mShouldReevaluateConstraints = true;
             }
-            long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
+            long newExemptedPeriodMs = Math.max(mAllowedTimePerPeriodMs[EXEMPTED_INDEX],
+                    Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS));
+            if (mBucketPeriodsMs[EXEMPTED_INDEX] != newExemptedPeriodMs) {
+                mBucketPeriodsMs[EXEMPTED_INDEX] = newExemptedPeriodMs;
+                mShouldReevaluateConstraints = true;
+            }
+            long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs[ACTIVE_INDEX],
                     Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
             if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
                 mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs;
                 mShouldReevaluateConstraints = true;
             }
-            long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+            long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs[WORKING_INDEX],
                     Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
             if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) {
                 mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs;
                 mShouldReevaluateConstraints = true;
             }
-            long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+            long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs[FREQUENT_INDEX],
                     Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
             if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) {
                 mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs;
                 mShouldReevaluateConstraints = true;
             }
-            long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs,
+            long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs[RARE_INDEX],
                     Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS));
             if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) {
                 mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
                 mShouldReevaluateConstraints = true;
             }
             // Fit in the range [allowed time (10 mins), 1 week].
-            long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+            long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs[RESTRICTED_INDEX],
                     Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS));
             if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) {
                 mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs;
@@ -3740,11 +3961,14 @@
             // Query the values as an atomic set.
             final DeviceConfig.Properties properties = DeviceConfig.getProperties(
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_EJ_LIMIT_EXEMPTED_MS,
                     KEY_EJ_LIMIT_ACTIVE_MS, KEY_EJ_LIMIT_WORKING_MS,
                     KEY_EJ_LIMIT_FREQUENT_MS, KEY_EJ_LIMIT_RARE_MS,
                     KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_ADDITION_SPECIAL_MS,
                     KEY_EJ_LIMIT_ADDITION_INSTALLER_MS,
                     KEY_EJ_WINDOW_SIZE_MS);
+            EJ_LIMIT_EXEMPTED_MS = properties.getLong(
+                    KEY_EJ_LIMIT_EXEMPTED_MS, DEFAULT_EJ_LIMIT_EXEMPTED_MS);
             EJ_LIMIT_ACTIVE_MS = properties.getLong(
                     KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS);
             EJ_LIMIT_WORKING_MS = properties.getLong(
@@ -3770,8 +3994,15 @@
                 mShouldReevaluateConstraints = true;
             }
             // The limit must be in the range [15 minutes, window size].
+            long newExemptLimitMs = Math.max(15 * MINUTE_IN_MILLIS,
+                    Math.min(newWindowSizeMs, EJ_LIMIT_EXEMPTED_MS));
+            if (mEJLimitsMs[EXEMPTED_INDEX] != newExemptLimitMs) {
+                mEJLimitsMs[EXEMPTED_INDEX] = newExemptLimitMs;
+                mShouldReevaluateConstraints = true;
+            }
+            // The limit must be in the range [15 minutes, exempted limit].
             long newActiveLimitMs = Math.max(15 * MINUTE_IN_MILLIS,
-                    Math.min(newWindowSizeMs, EJ_LIMIT_ACTIVE_MS));
+                    Math.min(newExemptLimitMs, EJ_LIMIT_ACTIVE_MS));
             if (mEJLimitsMs[ACTIVE_INDEX] != newActiveLimitMs) {
                 mEJLimitsMs[ACTIVE_INDEX] = newActiveLimitMs;
                 mShouldReevaluateConstraints = true;
@@ -3823,18 +4054,31 @@
             pw.println();
             pw.println("QuotaController:");
             pw.increaseIndent();
-            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println();
+            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)
+                    .println();
+            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS)
+                    .println();
+            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, ALLOWED_TIME_PER_PERIOD_WORKING_MS)
+                    .println();
+            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS)
+                    .println();
+            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, ALLOWED_TIME_PER_PERIOD_RARE_MS)
+                    .println();
+            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+                    ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS).println();
             pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, ALLOWED_TIME_SURPLUS_PRIORITY_LOW)
                     .println();
             pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, ALLOWED_TIME_SURPLUS_PRIORITY_MIN)
                     .println();
             pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println();
+            pw.print(KEY_WINDOW_SIZE_EXEMPTED_MS, WINDOW_SIZE_EXEMPTED_MS).println();
             pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
             pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
             pw.print(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println();
             pw.print(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println();
             pw.print(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println();
             pw.print(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println();
+            pw.print(KEY_MAX_JOB_COUNT_EXEMPTED, MAX_JOB_COUNT_EXEMPTED).println();
             pw.print(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println();
             pw.print(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
             pw.print(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
@@ -3843,6 +4087,7 @@
             pw.print(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
             pw.print(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
                     MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
+            pw.print(KEY_MAX_SESSION_COUNT_EXEMPTED, MAX_SESSION_COUNT_EXEMPTED).println();
             pw.print(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println();
             pw.print(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
             pw.print(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
@@ -3854,6 +4099,7 @@
                     TIMING_SESSION_COALESCING_DURATION_MS).println();
             pw.print(KEY_MIN_QUOTA_CHECK_DELAY_MS, MIN_QUOTA_CHECK_DELAY_MS).println();
 
+            pw.print(KEY_EJ_LIMIT_EXEMPTED_MS, EJ_LIMIT_EXEMPTED_MS).println();
             pw.print(KEY_EJ_LIMIT_ACTIVE_MS, EJ_LIMIT_ACTIVE_MS).println();
             pw.print(KEY_EJ_LIMIT_WORKING_MS, EJ_LIMIT_WORKING_MS).println();
             pw.print(KEY_EJ_LIMIT_FREQUENT_MS, EJ_LIMIT_FREQUENT_MS).println();
@@ -3875,8 +4121,6 @@
 
         private void dump(ProtoOutputStream proto) {
             final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER);
-            proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS,
-                    ALLOWED_TIME_PER_PERIOD_MS);
             proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS);
             proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS,
                     WINDOW_SIZE_ACTIVE_MS);
@@ -3946,7 +4190,7 @@
     //////////////////////// TESTING HELPERS /////////////////////////////
 
     @VisibleForTesting
-    long getAllowedTimePerPeriodMs() {
+    long[] getAllowedTimePerPeriodMs() {
         return mAllowedTimePerPeriodMs;
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 8b26397..b843dca 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -347,6 +347,22 @@
      */
     boolean mLinkCrossProfileApps =
             ConstantsObserver.DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS;
+
+    /**
+     * Duration (in millis) for the window where events occurring will be considered as
+     * broadcast response, starting from the point when an app receives a broadcast.
+     */
+    volatile long mBroadcastResponseWindowDurationMillis =
+            ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS;
+
+    /**
+     * Process state threshold that is used for deciding whether or not an app is in the background
+     * in the context of recording broadcast response stats. Apps whose process state is higher
+     * than this threshold state will be considered to be in background.
+     */
+    volatile int mBroadcastResponseFgThresholdState =
+            ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE;
+
     /**
      * Whether we should allow apps into the
      * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not.
@@ -1774,6 +1790,15 @@
         }
     }
 
+    @Override
+    public long getBroadcastResponseWindowDurationMs() {
+        return mBroadcastResponseWindowDurationMillis;
+    }
+
+    @Override
+    public int getBroadcastResponseFgThresholdState() {
+        return mBroadcastResponseFgThresholdState;
+    }
 
     @Override
     public void flushToDisk() {
@@ -2042,6 +2067,14 @@
         TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw);
         pw.println();
 
+        pw.print("  mBroadcastResponseWindowDurationMillis=");
+        TimeUtils.formatDuration(mBroadcastResponseWindowDurationMillis, pw);
+        pw.println();
+
+        pw.print("  mBroadcastResponseFgThresholdState=");
+        pw.print(ActivityManager.procStateToString(mBroadcastResponseFgThresholdState));
+        pw.println();
+
         pw.println();
         pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
         pw.print(" mAllowRestrictedBucket=");
@@ -2473,6 +2506,10 @@
                 KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "rare",
                 KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "restricted"
         };
+        private static final String KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+                "broadcast_response_window_timeout_ms";
+        private static final String KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE =
+                "broadcast_response_fg_threshold_state";
         public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS =
                 COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR;
         public static final long DEFAULT_STRONG_USAGE_TIMEOUT =
@@ -2502,6 +2539,10 @@
         public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
                 COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
         public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true;
+        public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+                2 * ONE_MINUTE;
+        public static final int DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE =
+                ActivityManager.PROCESS_STATE_TOP;
 
         ConstantsObserver(Handler handler) {
             super(handler);
@@ -2619,6 +2660,16 @@
                                     KEY_UNEXEMPTED_SYNC_SCHEDULED_HOLD_DURATION,
                                     DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT);
                             break;
+                        case KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS:
+                            mBroadcastResponseWindowDurationMillis = properties.getLong(
+                                    KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS,
+                                    DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS);
+                            break;
+                        case KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE:
+                            mBroadcastResponseFgThresholdState = properties.getInt(
+                                    KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE,
+                                    DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE);
+                            break;
                         default:
                             if (!timeThresholdsUpdated
                                     && (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD)
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index c5dc51c..e407e31 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -5,7 +5,9 @@
       "options": [
         {"include-filter": "android.app.usage.cts.UsageStatsTest"},
         {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.MediumTest"},
+        {"exclude-annotation": "androidx.test.filters.LargeTest"}
       ]
     },
     {
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index d963e68..b4edd39 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -61,6 +61,9 @@
         "test_com.android.media",
     ],
     min_sdk_version: "29",
+    lint: {
+        strict_updatability_linting: true,
+    },
     visibility: [
         "//frameworks/av/apex:__subpackages__",
         "//frameworks/base", // For framework-all
diff --git a/apex/media/framework/lint-baseline.xml b/apex/media/framework/lint-baseline.xml
index e1b1450..95eea45 100644
--- a/apex/media/framework/lint-baseline.xml
+++ b/apex/media/framework/lint-baseline.xml
@@ -1,312 +1,70 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev">
 
     <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`"
-        errorLine1="                            new ApplicationMediaCapabilities.Builder();"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {"
+        errorLine2="                                                        ~~~~~~~~~~~">
         <location
             file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
-            line="208"
-            column="29"/>
+            line="121"
+            column="57"/>
     </issue>
 
     <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`"
-        errorLine1="        ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder();"
-        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="            return String.format(&quot; session: {id: %d, status: %s, result: %s, progress: %d}&quot;,"
+        errorLine2="                   ^">
         <location
-            file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
-            line="314"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.os.RemoteException#rethrowFromSystemServer`"
-        errorLine1="            e.rethrowFromSystemServer();"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaCommunicationManager.java"
-            line="110"
-            column="15"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.os.Parcel#writeParcelableCreator`"
-        errorLine1="        dest.writeParcelableCreator((Parcelable) parcelable);"
-        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java"
-            line="77"
-            column="14"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.os.Parcel#readParcelableCreator`"
-        errorLine1="        return from.readParcelableCreator(loader);"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java"
-            line="82"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#mediaFormat`"
-        errorLine1="            this.mediaFormat = mediaFormat;"
-        errorLine2="            ~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="273"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#drmInitData`"
-        errorLine1="            this.drmInitData = drmInitData;"
-        errorLine2="            ~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="274"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
-        errorLine1="            this.timeMicros = timeMicros;"
-        errorLine2="            ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="295"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
-        errorLine1="            this.position = position;"
-        errorLine2="            ~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="296"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
-        errorLine1="            return &quot;[timeMicros=&quot; + timeMicros + &quot;, position=&quot; + position + &quot;]&quot;;"
-        errorLine2="                                                                 ~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="302"
-            column="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
-        errorLine1="            return &quot;[timeMicros=&quot; + timeMicros + &quot;, position=&quot; + position + &quot;]&quot;;"
-        errorLine2="                                    ~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="302"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level R (current min is 29): `android.media.MediaParser.SeekPoint`"
-        errorLine1="            SeekPoint other = (SeekPoint) obj;"
-        errorLine2="                               ~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="313"
-            column="32"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
-        errorLine1="            return timeMicros == other.timeMicros &amp;&amp; position == other.position;"
-        errorLine2="                                                     ~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="314"
-            column="54"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
-        errorLine1="            return timeMicros == other.timeMicros &amp;&amp; position == other.position;"
-        errorLine2="                                                                 ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="314"
-            column="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
-        errorLine1="            return timeMicros == other.timeMicros &amp;&amp; position == other.position;"
-        errorLine2="                   ~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="314"
+            file="frameworks/base/apex/media/framework/java/android/media/MediaTranscodingManager.java"
+            line="1651"
             column="20"/>
     </issue>
 
     <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
-        errorLine1="            return timeMicros == other.timeMicros &amp;&amp; position == other.position;"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~">
+        id="ParcelClassLoader"
+        message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead."
+        errorLine1="            Bundle out = parcel.readBundle(null);"
+        errorLine2="                                ~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="314"
-            column="34"/>
+            file="frameworks/base/apex/media/framework/java/android/media/MediaSession2.java"
+            line="303"
+            column="33"/>
     </issue>
 
     <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
-        errorLine1="            int result = (int) timeMicros;"
-        errorLine2="                               ~~~~~~~~~~">
+        id="ParcelClassLoader"
+        message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
+        errorLine1="        mCustomExtras = in.readBundle();"
+        errorLine2="                           ~~~~~~~~~~~~">
         <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="319"
-            column="32"/>
+            file="frameworks/base/apex/media/framework/java/android/media/Session2Command.java"
+            line="104"
+            column="28"/>
     </issue>
 
     <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
-        errorLine1="            result = 31 * result + (int) position;"
-        errorLine2="                                         ~~~~~~~~">
+        id="ParcelClassLoader"
+        message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead."
+        errorLine1="        mSessionLink = in.readParcelable(null);"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="320"
-            column="42"/>
+            file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java"
+            line="141"
+            column="27"/>
     </issue>
 
     <issue
-        id="NewApi"
-        message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
-        errorLine1="    public interface SeekableInputReader extends InputReader {"
-        errorLine2="                                                 ~~~~~~~~~~~">
+        id="ParcelClassLoader"
+        message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
+        errorLine1="        Bundle extras = in.readBundle();"
+        errorLine2="                           ~~~~~~~~~~~~">
         <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="352"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 31 (current min is 29): `android.media.metrics.LogSessionId#LOG_SESSION_ID_NONE`"
-        errorLine1="    @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1071"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `SeekableInputReader` to `InputReader` requires API level 30 (current min is 29)"
-        errorLine1="        mExoDataReader.mInputReader = seekableInputReader;"
-        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1201"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
-        errorLine1="            mPendingSeekPosition = seekPoint.position;"
-        errorLine2="                                   ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1287"
-            column="36"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
-        errorLine1="            mPendingSeekTimeMicros = seekPoint.timeMicros;"
-        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1288"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
-        errorLine1="            mExtractor.seek(seekPoint.position, seekPoint.timeMicros);"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1290"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
-        errorLine1="            mExtractor.seek(seekPoint.position, seekPoint.timeMicros);"
-        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1290"
-            column="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level R (current min is 29): `android.media.DrmInitData.SchemeInitData#uuid`"
-        errorLine1="                if (schemeInitData.uuid.equals(schemeUuid)) {"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1579"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
-        errorLine1="    private static final class DataReaderAdapter implements InputReader {"
-        errorLine2="                                                            ~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1872"
-            column="61"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
-        errorLine1="    private static final class ParsableByteArrayAdapter implements InputReader {"
-        errorLine2="                                                                   ~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
-            line="1905"
-            column="68"/>
+            file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java"
+            line="144"
+            column="28"/>
     </issue>
 
 </issues>
diff --git a/apex/media/service/Android.bp b/apex/media/service/Android.bp
index cf384ac..834e5cb 100644
--- a/apex/media/service/Android.bp
+++ b/apex/media/service/Android.bp
@@ -39,6 +39,7 @@
         ":service-media-s-sources",
     ],
     libs: [
+        "androidx.annotation_annotation",
         "updatable-media",
         "modules-annotation-minsdk",
         "modules-utils-build",
@@ -46,6 +47,9 @@
     jarjar_rules: "jarjar_rules.txt",
     sdk_version: "system_server_current",
     min_sdk_version: "29", // TODO: We may need to bump this at some point.
+    lint: {
+        strict_updatability_linting: true,
+    },
     apex_available: [
         "com.android.media",
     ],
diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
index 7d47e25..4223fa6 100644
--- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
+++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
@@ -46,6 +46,8 @@
 import android.util.SparseIntArray;
 import android.view.KeyEvent;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.modules.annotation.MinSdk;
 import com.android.server.SystemService;
@@ -63,6 +65,7 @@
  * @hide
  */
 @MinSdk(Build.VERSION_CODES.S)
+@RequiresApi(Build.VERSION_CODES.S)
 public class MediaCommunicationService extends SystemService {
     private static final String TAG = "MediaCommunicationSrv";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/apex/media/service/java/com/android/server/media/SessionPriorityList.java b/apex/media/service/java/com/android/server/media/SessionPriorityList.java
index 47b14b6..8145861 100644
--- a/apex/media/service/java/com/android/server/media/SessionPriorityList.java
+++ b/apex/media/service/java/com/android/server/media/SessionPriorityList.java
@@ -18,9 +18,13 @@
 
 import android.annotation.Nullable;
 import android.media.Session2Token;
+import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.GuardedBy;
+import com.android.modules.annotation.MinSdk;
 import com.android.server.media.MediaCommunicationService.Session2Record;
 
 import java.util.ArrayList;
@@ -33,6 +37,8 @@
  * Higher priority session has more chance to be selected as media button session,
  * which receives the media button events.
  */
+@MinSdk(Build.VERSION_CODES.S)
+@RequiresApi(Build.VERSION_CODES.S)
 class SessionPriorityList {
     private static final String TAG = "SessionPriorityList";
     private final Object mLock = new Object();
diff --git a/apex/media/service/lint-baseline.xml b/apex/media/service/lint-baseline.xml
index 05ce17c..def6baf 100644
--- a/apex/media/service/lint-baseline.xml
+++ b/apex/media/service/lint-baseline.xml
@@ -1,37 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
-        errorLine1="                    new MediaParceledListSlice&lt;>(getSession2TokensLocked(ALL.getIdentifier()));"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
-            line="242"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
-        errorLine1="            userSession2Tokens = new MediaParceledListSlice&lt;>(getSession2TokensLocked(userId));"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
-            line="243"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
-        errorLine1="                MediaParceledListSlice parceledListSlice = new MediaParceledListSlice&lt;>(result);"
-        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
-            line="386"
-            column="60"/>
-    </issue>
+<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev">
 
 </issues>
diff --git a/api/Android.bp b/api/Android.bp
index 3075d38..d57f5db 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -116,7 +116,6 @@
         "framework-graphics",
         "framework-media",
         "framework-mediaprovider",
-        "framework-nearby",
         "framework-permission",
         "framework-permission-s",
         "framework-scheduling",
diff --git a/api/api.go b/api/api.go
index aa9e399e..17649e8 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,7 +27,6 @@
 const art = "art.module.public.api"
 const conscrypt = "conscrypt.module.public.api"
 const i18n = "i18n.module.public.api"
-var modules_with_only_public_scope = []string{i18n, conscrypt}
 
 // The intention behind this soong plugin is to generate a number of "merged"
 // API-related modules that would otherwise require a large amount of very
@@ -149,8 +148,6 @@
 // This produces the same annotations.zip as framework-doc-stubs, but by using
 // outputs from individual modules instead of all the source code.
 func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
-	// Conscrypt and i18n currently do not enable annotations
-	modules = removeAll(modules, []string{conscrypt, i18n})
 	props := genruleProps{}
 	props.Name = proptools.StringPtr("sdk-annotations.zip")
 	props.Tools = []string{"merge_annotation_zips", "soong_zip"}
@@ -195,11 +192,8 @@
 
 func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
 	props := libraryProps{}
-	modules_with_system_stubs := removeAll(modules, modules_with_only_public_scope)
 	props.Name = proptools.StringPtr("all-modules-system-stubs")
-	props.Static_libs = append(
-		transformArray(modules_with_only_public_scope, "", ".stubs"),
-		transformArray(modules_with_system_stubs, "", ".stubs.system")...)
+	props.Static_libs = transformArray(modules, "", ".stubs.system")
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	ctx.CreateModule(java.LibraryFactory, &props)
@@ -226,8 +220,6 @@
 
 func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
 	var textFiles []MergedTxtDefinition
-	// Two module libraries currently do not support @SystemApi so only have the public scope.
-	bcpWithSystemApi := removeAll(bootclasspath, modules_with_only_public_scope)
 
 	tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
 	for i, f := range []string{"current.txt", "removed.txt"} {
@@ -241,14 +233,14 @@
 		textFiles = append(textFiles, MergedTxtDefinition{
 			TxtFilename: f,
 			BaseTxt:     ":non-updatable-system-" + f,
-			Modules:     bcpWithSystemApi,
+			Modules:     bootclasspath,
 			ModuleTag:   "{.system" + tagSuffix[i],
 			Scope:       "system",
 		})
 		textFiles = append(textFiles, MergedTxtDefinition{
 			TxtFilename: f,
 			BaseTxt:     ":non-updatable-module-lib-" + f,
-			Modules:     bcpWithSystemApi,
+			Modules:     bootclasspath,
 			ModuleTag:   "{.module-lib" + tagSuffix[i],
 			Scope:       "module-lib",
 		})
diff --git a/boot/Android.bp b/boot/Android.bp
index 8958d70..55ffe7c 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -80,10 +80,6 @@
             module: "com.android.mediaprovider-bootclasspath-fragment",
         },
         {
-            apex: "com.android.nearby",
-            module: "com.android.nearby-bootclasspath-fragment",
-        },
-        {
             apex: "com.android.os.statsd",
             module: "com.android.os.statsd-bootclasspath-fragment",
         },
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index dd33fdf..c8d2e0e 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -280,7 +280,7 @@
                 // Lower privacy policy (less restrictive) wins.
                 report->set_privacy_policy(args.getPrivacyPolicy());
             }
-            report->set_all_sections(report->all_sections() | args.all());
+            report->set_all_sections(report->all_sections() || args.all());
             for (int section: args.sections()) {
                 if (!has_section(*report, section)) {
                     report->add_section(section);
diff --git a/core/api/current.txt b/core/api/current.txt
index 922ad16..40f589d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -135,9 +135,6 @@
     field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA";
     field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
     field public static final String READ_LOGS = "android.permission.READ_LOGS";
-    field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
-    field public static final String READ_MEDIA_IMAGE = "android.permission.READ_MEDIA_IMAGE";
-    field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
     field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
@@ -223,8 +220,6 @@
     field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
     field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS";
     field public static final String PHONE = "android.permission-group.PHONE";
-    field public static final String READ_MEDIA_AURAL = "android.permission-group.READ_MEDIA_AURAL";
-    field public static final String READ_MEDIA_VISUAL = "android.permission-group.READ_MEDIA_VISUAL";
     field public static final String SENSORS = "android.permission-group.SENSORS";
     field public static final String SMS = "android.permission-group.SMS";
     field public static final String STORAGE = "android.permission-group.STORAGE";
@@ -331,6 +326,9 @@
     field public static final int allowClearUserData = 16842757; // 0x1010005
     field public static final int allowClickWhenDisabled = 16844312; // 0x1010618
     field public static final int allowEmbedded = 16843765; // 0x10103f5
+    field public static final int allowGameAngleDriver;
+    field public static final int allowGameDownscaling;
+    field public static final int allowGameFpsOverride;
     field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
     field public static final int allowParallelSyncs = 16843570; // 0x1010332
     field public static final int allowSingleTap = 16843353; // 0x1010259
@@ -734,6 +732,10 @@
     field public static final int freezesText = 16843116; // 0x101016c
     field public static final int fromAlpha = 16843210; // 0x10101ca
     field public static final int fromDegrees = 16843187; // 0x10101b3
+    field public static final int fromExtendBottom;
+    field public static final int fromExtendLeft;
+    field public static final int fromExtendRight;
+    field public static final int fromExtendTop;
     field public static final int fromId = 16843850; // 0x101044a
     field public static final int fromScene = 16843741; // 0x10103dd
     field public static final int fromXDelta = 16843206; // 0x10101c6
@@ -1436,10 +1438,12 @@
     field public static final int summaryOn = 16843247; // 0x10101ef
     field public static final int supportedTypes;
     field public static final int supportsAssist = 16844016; // 0x10104f0
+    field public static final int supportsBatteryGameMode;
     field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
     field public static final int supportsLocalInteraction = 16844047; // 0x101050f
     field public static final int supportsMultipleDisplays = 16844182; // 0x1010596
+    field public static final int supportsPerformanceGameMode;
     field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
     field public static final int supportsRtl = 16843695; // 0x10103af
     field public static final int supportsStylusHandwriting;
@@ -1579,6 +1583,10 @@
     field public static final int titleTextStyle = 16843512; // 0x10102f8
     field public static final int toAlpha = 16843211; // 0x10101cb
     field public static final int toDegrees = 16843188; // 0x10101b4
+    field public static final int toExtendBottom;
+    field public static final int toExtendLeft;
+    field public static final int toExtendRight;
+    field public static final int toExtendTop;
     field public static final int toId = 16843849; // 0x1010449
     field public static final int toScene = 16843742; // 0x10103de
     field public static final int toXDelta = 16843207; // 0x10101c7
@@ -3127,6 +3135,11 @@
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13; // 0xd
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
     field public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15; // 0xf
+    field public static final int GLOBAL_ACTION_DPAD_CENTER = 20; // 0x14
+    field public static final int GLOBAL_ACTION_DPAD_DOWN = 17; // 0x11
+    field public static final int GLOBAL_ACTION_DPAD_LEFT = 18; // 0x12
+    field public static final int GLOBAL_ACTION_DPAD_RIGHT = 19; // 0x13
+    field public static final int GLOBAL_ACTION_DPAD_UP = 16; // 0x10
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
     field public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10; // 0xa
     field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
@@ -7311,10 +7324,10 @@
     method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
     method public CharSequence getDeviceOwnerLockScreenInfo();
-    method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
     method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
     method @NonNull public String getEnrollmentSpecificId();
     method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
@@ -12225,7 +12238,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
     field public static final int TYPE_BUILTIN = 0; // 0x0
     field public static final int TYPE_DYNAMIC = 1; // 0x1
-    field public static final int TYPE_SDK = 3; // 0x3
+    field public static final int TYPE_SDK_PACKAGE = 3; // 0x3
     field public static final int TYPE_STATIC = 2; // 0x2
     field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
   }
@@ -12953,6 +12966,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.database.CursorWindow> CREATOR;
   }
 
+  public class CursorWindowAllocationException extends java.lang.RuntimeException {
+    ctor public CursorWindowAllocationException(@NonNull String);
+  }
+
   public class CursorWrapper implements android.database.Cursor {
     ctor public CursorWrapper(android.database.Cursor);
     method public void close();
@@ -13984,6 +14001,7 @@
     enum_constant @Deprecated public static final android.graphics.Bitmap.Config ARGB_4444;
     enum_constant public static final android.graphics.Bitmap.Config ARGB_8888;
     enum_constant public static final android.graphics.Bitmap.Config HARDWARE;
+    enum_constant public static final android.graphics.Bitmap.Config RGBA_1010102;
     enum_constant public static final android.graphics.Bitmap.Config RGBA_F16;
     enum_constant public static final android.graphics.Bitmap.Config RGB_565;
   }
@@ -22723,6 +22741,7 @@
     method public int describeContents();
     method @Nullable public String getClientPackageName();
     method public int getConnectionState();
+    method @NonNull public java.util.Set<java.lang.String> getDeduplicationIds();
     method @Nullable public CharSequence getDescription();
     method @Nullable public android.os.Bundle getExtras();
     method @NonNull public java.util.List<java.lang.String> getFeatures();
@@ -22756,6 +22775,7 @@
     method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
     method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
     method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setDeduplicationIds(@NonNull java.util.Set<java.lang.String>);
     method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
@@ -23282,8 +23302,12 @@
 
   public final class RouteDiscoveryPreference implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getAllowedPackages();
+    method @NonNull public java.util.List<java.lang.String> getDeduplicationPackageOrder();
     method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+    method @NonNull public java.util.List<java.lang.String> getRequiredFeatures();
     method public boolean shouldPerformActiveScan();
+    method public boolean shouldRemoveDuplicates();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
   }
@@ -23292,7 +23316,10 @@
     ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
     ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
     method @NonNull public android.media.RouteDiscoveryPreference build();
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setAllowedPackages(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setDeduplicationPackageOrder(@NonNull java.util.List<java.lang.String>);
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setRequiredFeatures(@NonNull java.util.List<java.lang.String>);
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
   }
 
@@ -31408,6 +31435,9 @@
     method @Nullable public byte[] createByteArray();
     method @Nullable public char[] createCharArray();
     method @Nullable public double[] createDoubleArray();
+    method @Nullable public <T> T createFixedArray(@NonNull Class<T>, @NonNull int...);
+    method @Nullable public <T, S extends android.os.IInterface> T createFixedArray(@NonNull Class<T>, @NonNull java.util.function.Function<android.os.IBinder,S>, @NonNull int...);
+    method @Nullable public <T, S extends android.os.Parcelable> T createFixedArray(@NonNull Class<T>, @NonNull android.os.Parcelable.Creator<S>, @NonNull int...);
     method @Nullable public float[] createFloatArray();
     method @Nullable public int[] createIntArray();
     method @Nullable public <T extends android.os.IInterface> T[] createInterfaceArray(@NonNull java.util.function.IntFunction<T[]>, @NonNull java.util.function.Function<android.os.IBinder,T>);
@@ -31449,6 +31479,9 @@
     method public void readException();
     method public void readException(int, String);
     method public android.os.ParcelFileDescriptor readFileDescriptor();
+    method public <T> void readFixedArray(@NonNull T);
+    method public <T, S extends android.os.IInterface> void readFixedArray(@NonNull T, @NonNull java.util.function.Function<android.os.IBinder,S>);
+    method public <T, S extends android.os.Parcelable> void readFixedArray(@NonNull T, @NonNull android.os.Parcelable.Creator<S>);
     method public float readFloat();
     method public void readFloatArray(@NonNull float[]);
     method @Deprecated @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
@@ -31492,6 +31525,7 @@
     method public void setDataCapacity(int);
     method public void setDataPosition(int);
     method public void setDataSize(int);
+    method public void setPropagateAllowBlocking();
     method public void unmarshall(@NonNull byte[], int, int);
     method public void writeArray(@Nullable Object[]);
     method public void writeBinderArray(@Nullable android.os.IBinder[]);
@@ -31509,6 +31543,7 @@
     method public void writeDoubleArray(@Nullable double[]);
     method public void writeException(@NonNull Exception);
     method public void writeFileDescriptor(@NonNull java.io.FileDescriptor);
+    method public <T> void writeFixedArray(@Nullable T, int, @NonNull int...);
     method public void writeFloat(float);
     method public void writeFloatArray(@Nullable float[]);
     method public void writeInt(int);
@@ -31729,9 +31764,14 @@
     method public void release();
     method public void release(int);
     method public void setReferenceCounted(boolean);
+    method public void setStateListener(@NonNull java.util.concurrent.Executor, @Nullable android.os.PowerManager.WakeLockStateListener);
     method public void setWorkSource(android.os.WorkSource);
   }
 
+  public static interface PowerManager.WakeLockStateListener {
+    method public void onStateChanged(boolean);
+  }
+
   public class Process {
     ctor public Process();
     method public static final long getElapsedCpuTime();
@@ -37882,6 +37922,7 @@
     method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
     method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
     method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
+    field public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
     field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
     field public static final String SERVICE_META_DATA = "android.autofill";
   }
@@ -38037,6 +38078,7 @@
   public final class FillRequest implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.os.Bundle getClientState();
+    method @Nullable public android.content.IntentSender getDelayedFillIntentSender();
     method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
     method public int getFlags();
     method public int getId();
@@ -38051,6 +38093,7 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+    field public static final int FLAG_DELAY_FILL = 4; // 0x4
     field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
     field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
   }
@@ -41196,6 +41239,7 @@
     field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
     field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
     field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
+    field public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool";
     field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
     field public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "lte_rsrq_thresholds_int_array";
     field public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "lte_rssnr_thresholds_int_array";
@@ -41280,6 +41324,7 @@
     field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string";
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
+    field public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string";
     field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
     field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool";
     field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool";
@@ -41323,6 +41368,7 @@
     field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
     field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
     field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
+    field public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000";
     field public static final int SERVICE_CLASS_NONE = 0; // 0x0
     field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
     field public static final int USSD_OVER_CS_ONLY = 2; // 0x2
@@ -51643,6 +51689,7 @@
     method public boolean isSelected();
     method public boolean isShowingHintText();
     method public boolean isTextEntryKey();
+    method public boolean isTextSelectable();
     method public boolean isVisibleToUser();
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
@@ -51706,6 +51753,7 @@
     method public void setStateDescription(@Nullable CharSequence);
     method public void setText(CharSequence);
     method public void setTextEntryKey(boolean);
+    method public void setTextSelectable(boolean);
     method public void setTextSelection(int, int);
     method public void setTooltipText(@Nullable CharSequence);
     method public void setTouchDelegateInfo(@NonNull android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 8ea1abc..6e7bc76 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -54,6 +54,21 @@
     method public void onCanceled(@NonNull android.app.PendingIntent);
   }
 
+  public class PropertyInvalidatedCache<Query, Result> {
+    ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
+    method public final void disableForCurrentProcess();
+    method public final void invalidateCache();
+    method public static void invalidateCache(int, @NonNull String);
+    method @Nullable public final Result query(@NonNull Query);
+    field public static final int MODULE_BLUETOOTH = 2; // 0x2
+  }
+
+  public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
+    ctor public PropertyInvalidatedCache.QueryHandler();
+    method @Nullable public abstract R apply(@NonNull Q);
+    method public boolean shouldBypassCache(@NonNull Q);
+  }
+
   public class StatusBarManager {
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
   }
@@ -63,9 +78,9 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void acknowledgeNewUserDisclaimer();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void acknowledgeNewUserDisclaimer();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getLogoutUser();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int logoutUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int logoutUser();
     field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
   }
 
@@ -126,6 +141,7 @@
 
   public abstract class PackageManager {
     method @NonNull public String getPermissionControllerPackageName();
+    method @NonNull public String getSupplementalProcessPackageName();
   }
 
 }
@@ -446,6 +462,17 @@
 
 }
 
+package android.net.netstats {
+
+  public class NetworkStatsDataMigrationUtils {
+    method @NonNull public static android.net.NetworkStatsCollection readPlatformCollection(@NonNull String, long) throws java.io.IOException;
+    field public static final String PREFIX_UID = "uid";
+    field public static final String PREFIX_UID_TAG = "uid_tag";
+    field public static final String PREFIX_XT = "xt";
+  }
+
+}
+
 package android.os {
 
   public final class BatteryStatsManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 171ee9a..1cba58c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -76,6 +76,7 @@
     field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
     field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+    field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
     field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
     field public static final String BRICK = "android.permission.BRICK";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
@@ -146,6 +147,7 @@
     field public static final String KILL_UID = "android.permission.KILL_UID";
     field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP";
     field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
+    field public static final String LOCATION_BYPASS = "android.permission.LOCATION_BYPASS";
     field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
     field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO";
     field public static final String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
@@ -165,6 +167,7 @@
     field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
     field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
+    field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
     field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
     field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
     field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
@@ -189,6 +192,7 @@
     field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
     field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
     field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
+    field public static final String MANAGE_WALLPAPER_EFFECTS_GENERATION = "android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION";
     field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
     field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
     field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
@@ -201,6 +205,7 @@
     field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
     field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
     field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
+    field public static final String MODIFY_TOUCH_MODE_STATE = "android.permission.MODIFY_TOUCH_MODE_STATE";
     field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
     field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
     field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -258,6 +263,7 @@
     field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
     field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
     field public static final String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS";
+    field public static final String RECEIVE_BLUETOOTH_MAP = "android.permission.RECEIVE_BLUETOOTH_MAP";
     field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
     field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
     field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
@@ -302,6 +308,7 @@
     field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
     field public static final String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
     field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION";
+    field public static final String SET_UNRESTRICTED_KEEP_CLEAR_AREAS = "android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS";
     field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT";
@@ -348,6 +355,7 @@
     field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
     field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
+    field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
   }
 
   public static final class Manifest.permission_group {
@@ -902,6 +910,19 @@
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarModeOverride();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setNavBarModeOverride(int);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferSenderDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+    field public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; // 0x0
+    field public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1; // 0x1
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST = 1; // 0x1
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST = 0; // 0x0
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER = 8; // 0x8
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED = 6; // 0x6
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED = 4; // 0x4
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED = 2; // 0x2
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED = 7; // 0x7
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED = 5; // 0x5
+    field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED = 3; // 0x3
     field public static final int NAV_BAR_MODE_OVERRIDE_KIDS = 1; // 0x1
     field public static final int NAV_BAR_MODE_OVERRIDE_NONE = 0; // 0x0
   }
@@ -1057,8 +1078,8 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
-    method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
-    method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
+    method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
+    method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
     method public boolean isDeviceManaged();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
@@ -1182,6 +1203,15 @@
     field public static final String WORK_PROFILE_PAUSED_TITLE = "MediaProvider.WORK_PROFILE_PAUSED_TITLE";
   }
 
+  public static final class DevicePolicyResources.Strings.PermissionController {
+    field public static final String BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE = "PermissionController.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE";
+    field public static final String BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = "PermissionController.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+    field public static final String FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = "PermissionController.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+    field public static final String HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE = "PermissionController.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE";
+    field public static final String LOCATION_AUTO_GRANTED_MESSAGE = "PermissionController.LOCATION_AUTO_GRANTED_MESSAGE";
+    field public static final String WORK_PROFILE_DEFAULT_APPS_TITLE = "PermissionController.WORK_PROFILE_DEFAULT_APPS_TITLE";
+  }
+
   public final class DevicePolicyStringResource implements android.os.Parcelable {
     ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int);
     method public int describeContents();
@@ -1194,6 +1224,7 @@
   public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
     method public boolean canDeviceOwnerGrantSensorsPermissions();
     method public int describeContents();
+    method @NonNull public android.os.PersistableBundle getAdminExtras();
     method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
     method public long getLocalTime();
     method @Nullable public java.util.Locale getLocale();
@@ -1207,6 +1238,7 @@
   public static final class FullyManagedDeviceProvisioningParams.Builder {
     ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setCanDeviceOwnerGrantSensorsPermissions(boolean);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
@@ -1217,6 +1249,7 @@
   public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.accounts.Account getAccountToMigrate();
+    method @NonNull public android.os.PersistableBundle getAdminExtras();
     method @NonNull public String getOwnerName();
     method @NonNull public android.content.ComponentName getProfileAdminComponentName();
     method @Nullable public String getProfileName();
@@ -1231,6 +1264,7 @@
     ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams build();
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account);
+    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepingAccountOnMigration(boolean);
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean);
@@ -2535,6 +2569,107 @@
 
 }
 
+package android.app.wallpapereffectsgeneration {
+
+  public final class CameraAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public float[] getAnchorPointInOutputUvSpace();
+    method @NonNull public float[] getAnchorPointInWorldSpace();
+    method public float getCameraOrbitPitchDegrees();
+    method public float getCameraOrbitYawDegrees();
+    method public float getDollyDistanceInWorldSpace();
+    method public float getFrustumFarInWorldSpace();
+    method public float getFrustumNearInWorldSpace();
+    method public float getVerticalFovDegrees();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CameraAttributes> CREATOR;
+  }
+
+  public static final class CameraAttributes.Builder {
+    ctor public CameraAttributes.Builder(@NonNull float[], @NonNull float[]);
+    method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes build();
+    method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setCameraOrbitPitchDegrees(@FloatRange(from=-90.0F, to=90.0f) float);
+    method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setCameraOrbitYawDegrees(@FloatRange(from=-180.0F, to=180.0f) float);
+    method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setDollyDistanceInWorldSpace(float);
+    method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setFrustumFarInWorldSpace(@FloatRange(from=0.0f) float);
+    method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setFrustumNearInWorldSpace(@FloatRange(from=0.0f) float);
+    method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setVerticalFovDegrees(@FloatRange(from=0.0f, to=180.0f, fromInclusive=false) float);
+  }
+
+  public final class CinematicEffectRequest implements android.os.Parcelable {
+    ctor public CinematicEffectRequest(@NonNull String, @NonNull android.graphics.Bitmap);
+    method public int describeContents();
+    method @NonNull public android.graphics.Bitmap getBitmap();
+    method @NonNull public String getTaskId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CinematicEffectRequest> CREATOR;
+  }
+
+  public final class CinematicEffectResponse implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.app.wallpapereffectsgeneration.CameraAttributes getEndKeyFrame();
+    method public int getImageContentType();
+    method @Nullable public android.app.wallpapereffectsgeneration.CameraAttributes getStartKeyFrame();
+    method public int getStatusCode();
+    method @NonNull public String getTaskId();
+    method @NonNull public java.util.List<android.app.wallpapereffectsgeneration.TexturedMesh> getTexturedMeshes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CINEMATIC_EFFECT_STATUS_ERROR = 2; // 0x2
+    field public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 3; // 0x3
+    field public static final int CINEMATIC_EFFECT_STATUS_OK = 1; // 0x1
+    field public static final int CINEMATIC_EFFECT_STATUS_PENDING = 4; // 0x4
+    field public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 5; // 0x5
+    field public static final int CINEMATIC_EFFECT_STATUS_UNKNOWN = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CinematicEffectResponse> CREATOR;
+    field public static final int IMAGE_CONTENT_TYPE_LANDSCAPE = 2; // 0x2
+    field public static final int IMAGE_CONTENT_TYPE_OTHER = 3; // 0x3
+    field public static final int IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT = 1; // 0x1
+    field public static final int IMAGE_CONTENT_TYPE_UNKNOWN = 0; // 0x0
+  }
+
+  public static final class CinematicEffectResponse.Builder {
+    ctor public CinematicEffectResponse.Builder(int, @NonNull String);
+    method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse build();
+    method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setEndKeyFrame(@Nullable android.app.wallpapereffectsgeneration.CameraAttributes);
+    method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setImageContentType(int);
+    method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setStartKeyFrame(@Nullable android.app.wallpapereffectsgeneration.CameraAttributes);
+    method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setTexturedMeshes(@NonNull java.util.List<android.app.wallpapereffectsgeneration.TexturedMesh>);
+  }
+
+  public final class TexturedMesh implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.graphics.Bitmap getBitmap();
+    method @NonNull public int[] getIndices();
+    method @NonNull public int getIndicesLayoutType();
+    method @NonNull public float[] getVertices();
+    method @NonNull public int getVerticesLayoutType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.TexturedMesh> CREATOR;
+    field public static final int INDICES_LAYOUT_TRIANGLES = 1; // 0x1
+    field public static final int INDICES_LAYOUT_UNDEFINED = 0; // 0x0
+    field public static final int VERTICES_LAYOUT_POSITION3_UV2 = 1; // 0x1
+    field public static final int VERTICES_LAYOUT_UNDEFINED = 0; // 0x0
+  }
+
+  public static final class TexturedMesh.Builder {
+    ctor public TexturedMesh.Builder(@NonNull android.graphics.Bitmap);
+    method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh build();
+    method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setIndices(@NonNull int[]);
+    method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setIndicesLayoutType(int);
+    method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setVertices(@NonNull float[]);
+    method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setVerticesLayoutType(int);
+  }
+
+  public final class WallpaperEffectsGenerationManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION) public void generateCinematicEffect(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager.CinematicEffectListener);
+  }
+
+  public static interface WallpaperEffectsGenerationManager.CinematicEffectListener {
+    method public void onCinematicEffectGenerated(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectResponse);
+  }
+
+}
+
 package android.apphibernation {
 
   public class AppHibernationManager {
@@ -2610,6 +2745,8 @@
 
   public final class VirtualDeviceParams implements android.os.Parcelable {
     method public int describeContents();
+    method @Nullable public java.util.Set<android.content.ComponentName> getAllowedActivities();
+    method @Nullable public java.util.Set<android.content.ComponentName> getBlockedActivities();
     method public int getLockState();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -2621,6 +2758,8 @@
   public static final class VirtualDeviceParams.Builder {
     ctor public VirtualDeviceParams.Builder();
     method @NonNull public android.companion.virtual.VirtualDeviceParams build();
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@Nullable java.util.Set<android.content.ComponentName>);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@Nullable java.util.Set<android.content.ComponentName>);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
   }
@@ -2678,6 +2817,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int);
     method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
+    method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions);
     method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
     field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
@@ -2688,7 +2828,7 @@
     field public static final String BATTERY_STATS_SERVICE = "batterystats";
     field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
     field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
-    field public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service";
+    field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
     field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
     field public static final String ETHERNET_SERVICE = "ethernet";
@@ -2718,6 +2858,7 @@
     field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
     field public static final String UWB_SERVICE = "uwb";
     field public static final String VR_SERVICE = "vrmanager";
+    field public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE = "wallpaper_effects_generation";
     field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
     field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
@@ -2781,6 +2922,7 @@
     field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
     field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
     field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
+    field public static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED";
     field @RequiresPermission(android.Manifest.permission.START_VIEW_APP_FEATURES) public static final String ACTION_VIEW_APP_FEATURES = "android.intent.action.VIEW_APP_FEATURES";
     field public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
     field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
@@ -2803,7 +2945,9 @@
     field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
     field public static final String EXTRA_SHOWING_ATTRIBUTION = "android.intent.extra.SHOWING_ATTRIBUTION";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
+    field public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+    field public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 16777216; // 0x1000000
     field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
     field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
   }
@@ -3686,6 +3830,7 @@
     method public void sendKeyEvent(int, boolean);
     method public void sendVendorCommand(int, byte[], boolean);
     method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener);
+    method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener, int);
   }
 
   public static interface HdmiClient.OnDeviceSelectedListener {
@@ -5534,9 +5679,9 @@
     ctor public LastLocationRequest.Builder();
     ctor public LastLocationRequest.Builder(@NonNull android.location.LastLocationRequest);
     method @NonNull public android.location.LastLocationRequest build();
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LastLocationRequest.Builder setAdasGnssBypass(boolean);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LastLocationRequest.Builder setAdasGnssBypass(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LastLocationRequest.Builder setHiddenFromAppOps(boolean);
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean);
   }
 
   public class Location implements android.os.Parcelable {
@@ -5565,7 +5710,7 @@
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAdasGnssLocationEnabled(boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public void setAdasGnssLocationEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackage(@Nullable String);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
@@ -5598,7 +5743,7 @@
     method @Deprecated @NonNull public android.location.LocationRequest setFastestInterval(long);
     method @Deprecated public void setHideFromAppOps(boolean);
     method @Deprecated @NonNull public android.location.LocationRequest setInterval(long);
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
+    method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
     method @Deprecated @NonNull public android.location.LocationRequest setLowPowerMode(boolean);
     method @Deprecated @NonNull public android.location.LocationRequest setNumUpdates(int);
     method @Deprecated @NonNull public android.location.LocationRequest setProvider(@NonNull String);
@@ -5614,9 +5759,9 @@
   }
 
   public static final class LocationRequest.Builder {
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setAdasGnssBypass(boolean);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest.Builder setAdasGnssBypass(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LocationRequest.Builder setHiddenFromAppOps(boolean);
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public android.location.LocationRequest.Builder setLowPower(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
   }
@@ -5868,6 +6013,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
     method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
+    field public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION";
     field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
     field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
     field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
@@ -5876,7 +6022,11 @@
     field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
     field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
     field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
+    field public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
+    field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE";
+    field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
+    field public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6
     field public static final int SUCCESS = 0; // 0x0
   }
 
@@ -8918,6 +9068,8 @@
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int);
@@ -8983,6 +9135,7 @@
   }
 
   public static class Build.VERSION {
+    field @NonNull public static final java.util.Set<java.lang.String> KNOWN_CODENAMES;
     field @NonNull public static final String PREVIEW_SDK_FINGERPRINT;
   }
 
@@ -10219,6 +10372,7 @@
     method @Deprecated public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
     method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, @Nullable String, boolean);
     field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
+    field public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
     field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
     field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
@@ -10259,6 +10413,8 @@
   }
 
   public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
+    method public static int getIntForUser(@NonNull android.content.ContentResolver, @NonNull String, int, int);
+    method @Nullable public static String getStringForUser(@NonNull android.content.ContentResolver, @NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
     field @Deprecated public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED = "accessibility_display_magnification_navbar_enabled";
@@ -10610,6 +10766,8 @@
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onCancelAttentionCheck(@NonNull android.service.attention.AttentionService.AttentionCallback);
     method public abstract void onCheckAttention(@NonNull android.service.attention.AttentionService.AttentionCallback);
+    method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityCallback);
+    method public void onStopProximityUpdates();
     field public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; // 0x6
     field public static final int ATTENTION_FAILURE_CANCELLED = 3; // 0x3
     field public static final int ATTENTION_FAILURE_PREEMPTED = 4; // 0x4
@@ -10617,6 +10775,7 @@
     field public static final int ATTENTION_FAILURE_UNKNOWN = 2; // 0x2
     field public static final int ATTENTION_SUCCESS_ABSENT = 0; // 0x0
     field public static final int ATTENTION_SUCCESS_PRESENT = 1; // 0x1
+    field public static final double PROXIMITY_UNKNOWN = -1.0;
     field public static final String SERVICE_INTERFACE = "android.service.attention.AttentionService";
   }
 
@@ -10625,6 +10784,10 @@
     method public void onSuccess(int, long);
   }
 
+  public static final class AttentionService.ProximityCallback {
+    method public void onProximityUpdate(double);
+  }
+
 }
 
 package android.service.autofill {
@@ -11034,7 +11197,7 @@
 
   public class GameService extends android.app.Service {
     ctor public GameService();
-    method public final void createGameSession(@IntRange(from=0) int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final void createGameSession(@IntRange(from=0) int);
     method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
@@ -11048,7 +11211,8 @@
     method public void onCreate();
     method public void onDestroy();
     method public void onGameTaskFocusChanged(boolean);
-    method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public final boolean restartGame();
+    method public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame();
     method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams);
     method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback);
   }
@@ -11477,6 +11641,7 @@
     method @Deprecated public final void grantTrust(CharSequence, long, boolean);
     method public final void grantTrust(CharSequence, long, int);
     method public final void isEscrowTokenActive(long, android.os.UserHandle);
+    method public final void lockUser();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public boolean onConfigure(java.util.List<android.os.PersistableBundle>);
     method public void onDeviceLocked();
@@ -11487,13 +11652,16 @@
     method public void onEscrowTokenStateReceived(long, int);
     method public void onTrustTimeout();
     method public void onUnlockAttempt(boolean);
+    method public void onUserRequestedUnlock();
     method public final void removeEscrowToken(long, android.os.UserHandle);
     method public final void revokeTrust();
     method public final void setManagingTrust(boolean);
     method public final void showKeyguardErrorMessage(@NonNull CharSequence);
     method public final void unlockUserWithToken(long, byte[], android.os.UserHandle);
     field public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 2; // 0x2
+    field public static final int FLAG_GRANT_TRUST_DISPLAY_MESSAGE = 8; // 0x8
     field public static final int FLAG_GRANT_TRUST_INITIATED_BY_USER = 1; // 0x1
+    field public static final int FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE = 4; // 0x4
     field public static final String SERVICE_INTERFACE = "android.service.trust.TrustAgentService";
     field public static final int TOKEN_STATE_ACTIVE = 1; // 0x1
     field public static final int TOKEN_STATE_INACTIVE = 0; // 0x0
@@ -11669,6 +11837,17 @@
 
 }
 
+package android.service.wallpapereffectsgeneration {
+
+  public abstract class WallpaperEffectsGenerationService extends android.app.Service {
+    ctor public WallpaperEffectsGenerationService();
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onGenerateCinematicEffect(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectRequest);
+    method public final void returnCinematicEffectResponse(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectResponse);
+  }
+
+}
+
 package android.service.watchdog {
 
   public abstract class ExplicitHealthCheckService extends android.app.Service {
@@ -11843,6 +12022,7 @@
 
   public abstract class ConnectionService extends android.app.Service {
     method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.Connection onCreateUnknownConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest);
   }
 
   public abstract class InCallService extends android.app.Service {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index dbb4274..e17a9bb 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -294,7 +294,6 @@
 
 
 ServiceName: android.content.Context#CLOUDSEARCH_SERVICE:
-    Inconsistent service value; expected `cloudsearch`, was `cloudsearch_service` (Note: Do not change the name of already released services, which will break tools using `adb shell dumpsys`. Instead add `@SuppressLint("ServiceName"))`
 
 
 UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os.UserHandle):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 39e12f4..fea7396 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -278,6 +278,7 @@
   }
 
   public final class GameManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public boolean isAngleEnabled(@NonNull String);
     method public void setGameServiceProvider(@Nullable String);
   }
 
@@ -355,6 +356,32 @@
     ctor public PictureInPictureUiState(boolean);
   }
 
+  public class PropertyInvalidatedCache<Query, Result> {
+    ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
+    method @NonNull public static String createPropertyName(int, @NonNull String);
+    method public final void disableForCurrentProcess();
+    method public static void disableForTestMode();
+    method public final void disableInstance();
+    method public final void disableSystemWide();
+    method public final void forgetDisableLocal();
+    method public boolean getDisabledState();
+    method public final void invalidateCache();
+    method public static void invalidateCache(int, @NonNull String);
+    method public final boolean isDisabled();
+    method @Nullable public final Result query(@NonNull Query);
+    method public static void setTestMode(boolean);
+    method public void testPropertyName();
+    field public static final int MODULE_BLUETOOTH = 2; // 0x2
+    field public static final int MODULE_SYSTEM = 1; // 0x1
+    field public static final int MODULE_TEST = 0; // 0x0
+  }
+
+  public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
+    ctor public PropertyInvalidatedCache.QueryHandler();
+    method @Nullable public abstract R apply(@NonNull Q);
+    method public boolean shouldBypassCache(@NonNull Q);
+  }
+
   public class StatusBarManager {
     method public void cancelRequestAddTile(@NonNull String);
     method public void clickNotification(@Nullable String, int, int, boolean);
@@ -462,6 +489,7 @@
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
     method public void forceUpdateUserSetupComplete(int);
     method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
+    method public int getDeviceOwnerType(@NonNull android.content.ComponentName);
     method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String);
     method public long getLastBugReportRequestTime();
     method public long getLastNetworkLogRetrievalTime();
@@ -470,6 +498,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps();
     method public boolean isCurrentInputMethodSetByOwner();
     method public boolean isFactoryResetProtectionPolicySupported();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged();
     method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
     method @NonNull public static String operationSafetyReasonToString(int);
     method @NonNull public static String operationToString(int);
@@ -477,6 +506,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int);
+    method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
@@ -772,6 +802,7 @@
     method @NonNull public String getPermissionControllerPackageName();
     method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
     method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
+    method @NonNull public String getSupplementalProcessPackageName();
     method @Nullable public String getSystemTextClassifierPackageName();
     method @Nullable public String getWellbeingPackageName();
     method public void holdLock(android.os.IBinder, int);
@@ -1092,7 +1123,7 @@
 package android.hardware.devicestate {
 
   public final class DeviceStateManager {
-    method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
+    method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest();
     method @NonNull public int[] getSupportedStates();
     method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
     method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
@@ -1710,8 +1741,11 @@
 
   public final class Parcel {
     method public boolean allowSquashing();
+    method public int getFlags();
     method public int readExceptionCode();
     method public void restoreAllowSquashing(boolean);
+    field public static final int FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT = 1; // 0x1
+    field public static final int FLAG_PROPAGATE_ALLOW_BLOCKING = 2; // 0x2
   }
 
   public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
@@ -2710,6 +2744,7 @@
     method @NonNull public android.view.Display.Mode getDefaultMode();
     method @NonNull public int[] getReportedHdrTypes();
     method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
+    method @Nullable public android.view.Display.Mode getSystemPreferredDisplayMode();
     method public int getType();
     method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
     method public boolean hasAccess(int);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 8234f03..5649c5f 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -128,6 +128,7 @@
         "android/os/IThermalStatusListener.aidl",
         "android/os/IThermalService.aidl",
         "android/os/IPowerManager.aidl",
+        "android/os/IWakeLockCallback.aidl",
     ],
 }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 6b0aef8..42d2d28 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -571,6 +571,31 @@
      */
     public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15;
 
+    /**
+     * Action to trigger dpad up keyevent.
+     */
+    public static final int GLOBAL_ACTION_DPAD_UP = 16;
+
+    /**
+     * Action to trigger dpad down keyevent.
+     */
+    public static final int GLOBAL_ACTION_DPAD_DOWN = 17;
+
+    /**
+     * Action to trigger dpad left keyevent.
+     */
+    public static final int GLOBAL_ACTION_DPAD_LEFT = 18;
+
+    /**
+     * Action to trigger dpad right keyevent.
+     */
+    public static final int GLOBAL_ACTION_DPAD_RIGHT = 19;
+
+    /**
+     * Action to trigger dpad center keyevent.
+     */
+    public static final int GLOBAL_ACTION_DPAD_CENTER = 20;
+
     private static final String LOG_TAG = "AccessibilityService";
 
     /**
@@ -2328,10 +2353,16 @@
      * @param action The action to perform.
      * @return Whether the action was successfully performed.
      *
+     * Perform actions using ids like the id constants referenced below:
      * @see #GLOBAL_ACTION_BACK
      * @see #GLOBAL_ACTION_HOME
      * @see #GLOBAL_ACTION_NOTIFICATIONS
      * @see #GLOBAL_ACTION_RECENTS
+     * @see #GLOBAL_ACTION_DPAD_UP
+     * @see #GLOBAL_ACTION_DPAD_DOWN
+     * @see #GLOBAL_ACTION_DPAD_LEFT
+     * @see #GLOBAL_ACTION_DPAD_RIGHT
+     * @see #GLOBAL_ACTION_DPAD_CENTER
      */
     public final boolean performGlobalAction(int action) {
         IAccessibilityServiceConnection connection =
diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java
index bb2b8d4..735df80 100644
--- a/core/java/android/accessibilityservice/TouchInteractionController.java
+++ b/core/java/android/accessibilityservice/TouchInteractionController.java
@@ -376,7 +376,7 @@
             throw new IllegalStateException(
                     "State transitions are not allowed without first adding a callback.");
         }
-        if (mState != STATE_TOUCH_INTERACTING) {
+        if (mState != STATE_TOUCH_INTERACTING && mState != STATE_DRAGGING) {
             throw new IllegalStateException(
                     "State transitions are not allowed in " + stateToString(mState));
         }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3b2176e..3289304 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -200,6 +200,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SomeArgs;
@@ -1004,6 +1005,11 @@
         RemoteCallback finishCallback;
     }
 
+    static final class DumpResourcesData {
+        public ParcelFileDescriptor fd;
+        public RemoteCallback finishCallback;
+    }
+
     static final class UpdateCompatibilityData {
         String pkg;
         CompatibilityInfo info;
@@ -1315,6 +1321,20 @@
             sendMessage(H.SCHEDULE_CRASH, args, typeId);
         }
 
+        @Override
+        public void dumpResources(ParcelFileDescriptor fd, RemoteCallback callback) {
+            DumpResourcesData data = new DumpResourcesData();
+            try {
+                data.fd = fd.dup();
+                data.finishCallback = callback;
+                sendMessage(H.DUMP_RESOURCES, data, 0, 0, false /*async*/);
+            } catch (IOException e) {
+                Slog.w(TAG, "dumpResources failed", e);
+            } finally {
+                IoUtils.closeQuietly(fd);
+            }
+        }
+
         public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken,
                 String prefix, String[] args) {
             DumpComponentInfo data = new DumpComponentInfo();
@@ -2038,6 +2058,7 @@
         public static final int UPDATE_UI_TRANSLATION_STATE = 163;
         public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164;
         public static final int DUMP_GFXINFO = 165;
+        public static final int DUMP_RESOURCES = 166;
 
         public static final int INSTRUMENT_WITHOUT_RESTART = 170;
         public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
@@ -2091,6 +2112,7 @@
                     case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
                     case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                         return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+                    case DUMP_RESOURCES: return "DUMP_RESOURCES";
                 }
             }
             return Integer.toString(code);
@@ -2206,6 +2228,9 @@
                 case DUMP_HEAP:
                     handleDumpHeap((DumpHeapData) msg.obj);
                     break;
+                case DUMP_RESOURCES:
+                    handleDumpResources((DumpResourcesData) msg.obj);
+                    break;
                 case DUMP_ACTIVITY:
                     handleDumpActivity((DumpComponentInfo)msg.obj);
                     break;
@@ -4584,6 +4609,23 @@
         }
     }
 
+    private void handleDumpResources(DumpResourcesData info) {
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+        try {
+            PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+                    info.fd.getFileDescriptor()));
+
+            Resources.dumpHistory(pw, "");
+            pw.flush();
+            if (info.finishCallback != null) {
+                info.finishCallback.sendResult(null);
+            }
+        } finally {
+            IoUtils.closeQuietly(info.fd);
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
     private void handleDumpActivity(DumpComponentInfo info) {
         final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
         try {
@@ -7812,6 +7854,8 @@
         MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
         MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
         BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
+        BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
+            BinderCallsStats.startForBluetooth(context); });
     }
 
     private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0d1bc05..fdf37f6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2363,11 +2363,11 @@
             Manifest.permission.USE_BIOMETRIC,
             Manifest.permission.ACTIVITY_RECOGNITION,
             Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
-            Manifest.permission.READ_MEDIA_AUDIO,
+            null,
             null, // no permission for OP_WRITE_MEDIA_AUDIO
-            Manifest.permission.READ_MEDIA_VIDEO,
+            null,
             null, // no permission for OP_WRITE_MEDIA_VIDEO
-            Manifest.permission.READ_MEDIA_IMAGE,
+            null,
             null, // no permission for OP_WRITE_MEDIA_IMAGES
             null, // no permission for OP_LEGACY_STORAGE
             null, // no permission for OP_ACCESS_ACCESSIBILITY
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7b55b6c..20ffa25 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,6 +16,11 @@
 
 package android.app;
 
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE;
 import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
 import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
 import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
@@ -31,6 +36,7 @@
 import android.annotation.StringRes;
 import android.annotation.UserIdInt;
 import android.annotation.XmlRes;
+import android.app.admin.DevicePolicyManager;
 import android.app.role.RoleManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -62,7 +68,6 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -74,7 +79,6 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.ArtManager;
-import android.content.pm.pkg.FrameworkPackageUserState;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -122,7 +126,6 @@
 
 import libcore.util.EmptyArray;
 
-import java.io.File;
 import java.lang.ref.WeakReference;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
@@ -172,6 +175,8 @@
     private PackageInstaller mInstaller;
     @GuardedBy("mLock")
     private ArtManager mArtManager;
+    @GuardedBy("mLock")
+    private DevicePolicyManager mDevicePolicyManager;
 
     @GuardedBy("mDelegates")
     private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
@@ -188,6 +193,15 @@
         }
     }
 
+    DevicePolicyManager getDevicePolicyManager() {
+        synchronized (mLock) {
+            if (mDevicePolicyManager == null) {
+                mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+            }
+            return mDevicePolicyManager;
+        }
+    }
+
     private PermissionManager getPermissionManager() {
         synchronized (mLock) {
             if (mPermissionManager == null) {
@@ -849,6 +863,18 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public String getSupplementalProcessPackageName() {
+        try {
+            return mPM.getSupplementalProcessPackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @Override
     public boolean addPermission(PermissionInfo info) {
         return getPermissionManager().addPermission(info, false);
@@ -1876,12 +1902,27 @@
         if (!hasUserBadge(user.getIdentifier())) {
             return icon;
         }
+
+        final Drawable badgeForeground = getDevicePolicyManager().getDrawable(
+                getUpdatableUserIconBadgeId(user),
+                SOLID_COLORED,
+                () -> getDefaultUserIconBadge(user));
+
         Drawable badge = new LauncherIcons(mContext).getBadgeDrawable(
-                getUserManager().getUserIconBadgeResId(user.getIdentifier()),
+                badgeForeground,
                 getUserBadgeColor(user, false));
         return getBadgedDrawable(icon, badge, null, true);
     }
 
+    private String getUpdatableUserIconBadgeId(UserHandle user) {
+        return getUserManager().isManagedProfile(user.getIdentifier())
+                ? WORK_PROFILE_ICON_BADGE : UNDEFINED;
+    }
+
+    private Drawable getDefaultUserIconBadge(UserHandle user) {
+        return mContext.getDrawable(getUserManager().getUserIconBadgeResId(user.getIdentifier()));
+    }
+
     @Override
     public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user,
             Rect badgeLocation, int badgeDensity) {
@@ -1913,13 +1954,28 @@
         if (badgeColor == null) {
             return null;
         }
-        Drawable badgeForeground = getDrawableForDensity(
-                getUserManager().getUserBadgeResId(user.getIdentifier()), density);
+
+        final Drawable badgeForeground = getDevicePolicyManager().getDrawableForDensity(
+                getUpdatableUserBadgeId(user),
+                SOLID_COLORED,
+                density,
+                () -> getDefaultUserBadgeForDensity(user, density));
+
         badgeForeground.setTint(getUserBadgeColor(user, false));
         Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground });
         return badge;
     }
 
+    private String getUpdatableUserBadgeId(UserHandle user) {
+        return getUserManager().isManagedProfile(user.getIdentifier())
+                ? WORK_PROFILE_ICON : UNDEFINED;
+    }
+
+    private Drawable getDefaultUserBadgeForDensity(UserHandle user, int density) {
+        return getDrawableForDensity(
+                getUserManager().getUserBadgeResId(user.getIdentifier()), density);
+    }
+
     /**
      * Returns the badge color based on whether device has dark theme enabled or not.
      */
@@ -1928,14 +1984,24 @@
         if (!hasUserBadge(user.getIdentifier())) {
             return null;
         }
-        Drawable badge = getDrawableForDensity(
-                getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
+
+        final Drawable badge = getDevicePolicyManager().getDrawableForDensity(
+                getUpdatableUserBadgeId(user),
+                SOLID_NOT_COLORED,
+                density,
+                () -> getDefaultUserBadgeNoBackgroundForDensity(user, density));
+
         if (badge != null) {
             badge.setTint(getUserBadgeColor(user, true));
         }
         return badge;
     }
 
+    private Drawable getDefaultUserBadgeNoBackgroundForDensity(UserHandle user, int density) {
+        return getDrawableForDensity(
+                getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
+    }
+
     private Drawable getDrawableForDensity(int drawableId, int density) {
         if (density <= 0) {
             density = mContext.getResources().getDisplayMetrics().densityDpi;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index db58c21..7845b6a 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -37,7 +37,8 @@
 @Immutable
 @DataClass(genEqualsHashCode = true,
         genAidl = true,
-        genHiddenConstructor = true)
+        genHiddenConstructor = true,
+        genToString = true)
 // - We don't expose the opCode, but rather the public name of the op, hence use a non-standard
 //   getter
 @DataClass.Suppress({"getOpCode"})
@@ -70,9 +71,13 @@
         Preconditions.checkArgumentInRange(mOpCode, 0, AppOpsManager._NUM_OP - 1, "opCode");
     }
 
+    private String opCodeToString() {
+        return getOp();
+    }
 
 
-    // Code below generated by codegen v1.0.20.
+
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -160,6 +165,21 @@
 
     @Override
     @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "AsyncNotedAppOp { " +
+                "opCode = " + opCodeToString() + ", " +
+                "notingUid = " + mNotingUid + ", " +
+                "attributionTag = " + mAttributionTag + ", " +
+                "message = " + mMessage + ", " +
+                "time = " + mTime +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
     public boolean equals(@Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
         // boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
@@ -261,10 +281,10 @@
     };
 
     @DataClass.Generated(
-            time = 1604456255752L,
-            codegenVersion = "1.0.20",
+            time = 1643320606160L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
-            inputSignatures = "private final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.IntRange int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.CurrentTimeMillisLong long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nprivate  void onConstructed()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.IntRange int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.CurrentTimeMillisLong long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nprivate  void onConstructed()\nprivate  java.lang.String opCodeToString()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f3315a8..8181a74 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1997,7 +1997,8 @@
 
     private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
             String instanceName, Handler handler, Executor executor, UserHandle user) {
-        // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
+        // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and
+        // ActivityManagerService.LocalService.startAndBindSupplementalProcessService
         IServiceConnection sd;
         if (conn == null) {
             throw new IllegalArgumentException("connection is null");
@@ -2023,10 +2024,10 @@
                 flags |= BIND_WAIVE_PRIORITY;
             }
             service.prepareToLeaveProcess(this);
-            int res = ActivityManager.getService().bindIsolatedService(
-                mMainThread.getApplicationThread(), getActivityToken(), service,
-                service.resolveTypeIfNeeded(getContentResolver()),
-                sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
+            int res = ActivityManager.getService().bindServiceInstance(
+                    mMainThread.getApplicationThread(), getActivityToken(), service,
+                    service.resolveTypeIfNeeded(getContentResolver()),
+                    sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
             if (res < 0) {
                 throw new SecurityException(
                         "Not allowed to bind to service " + service);
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 289b348..040399e 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -181,14 +181,18 @@
     /**
      * Returns if ANGLE is enabled for a given package and user ID.
      * <p>
+     * ANGLE (Almost Native Graphics Layer Engine) can translate OpenGL ES commands to Vulkan
+     * commands. Enabling ANGLE may improve the performance and/or reduce the power consumption of
+     * applications.
      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
      *
      * @hide
      */
+    @TestApi
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
-    public @GameMode boolean getAngleEnabled(@NonNull String packageName) {
+    public @GameMode boolean isAngleEnabled(@NonNull String packageName) {
         try {
-            return mService.getAngleEnabled(packageName, mContext.getUserId());
+            return mService.isAngleEnabled(packageName, mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 82c419a..e4ef12c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -169,7 +169,7 @@
     int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
             in String resolvedType, in IServiceConnection connection, int flags,
             in String callingPackage, int userId);
-    int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service,
+    int bindServiceInstance(in IApplicationThread caller, in IBinder token, in Intent service,
             in String resolvedType, in IServiceConnection connection, int flags,
             in String instanceName, in String callingPackage, int userId);
     void updateServiceGroup(in IServiceConnection connection, int group, int importance);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 1714229..77657d5 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -113,6 +113,7 @@
             in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
     void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
             in String[] args);
+    void dumpResources(in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
     void clearDnsCache();
     void updateHttpProxy();
     void setCoreSettings(in Bundle coreSettings);
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 3ea07676..7035ac0 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -26,7 +26,7 @@
     int getGameMode(String packageName, int userId);
     void setGameMode(String packageName, int gameMode, int userId);
     int[] getAvailableGameModes(String packageName);
-    boolean getAngleEnabled(String packageName, int userId);
+    boolean isAngleEnabled(String packageName, int userId);
     void setGameState(String packageName, in GameState gameState, int userId);
     GameModeInfo getGameModeInfo(String packageName, int userId);
     void setGameServiceProvider(String packageName);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 14afd0f..5d1f4df 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -746,7 +746,7 @@
         if (!hasPermission(Manifest.permission.SET_INITIAL_LOCK)) {
             throw new SecurityException("Requires SET_INITIAL_LOCK permission.");
         }
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+        return true;
     }
 
     private boolean hasPermission(String permission) {
@@ -814,6 +814,8 @@
     /**
     * Set the lockscreen password after validating against its expected complexity level.
     *
+    * Below {@link android.os.Build.VERSION_CODES#S_V2}, this API will only work
+    * when {@link PackageManager.FEATURE_AUTOMOTIVE} is present.
     * @param lockType - type of lock as specified in {@link LockTypes}
     * @param password - password to validate; this has the same encoding
     *        as the output of String#getBytes
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 4e32e9a..56c725e 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -746,6 +746,10 @@
                 // default linker namespace.
                 continue;
             }
+            if (info.isSdk()) {
+                // SDKs are not loaded automatically.
+                continue;
+            }
             if (libsToLoadAfter.contains(info.getName())) {
                 if (DEBUG) {
                     Slog.v(ActivityThread.TAG,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d57c288..6a14772 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -17,6 +17,10 @@
 package android.app;
 
 import static android.annotation.Dimension.DP;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
 import static android.graphics.drawable.Icon.TYPE_URI;
 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
 
@@ -39,6 +43,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.DevicePolicyManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -71,6 +76,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.text.BidiFormatter;
 import android.text.SpannableStringBuilder;
@@ -5046,6 +5052,18 @@
             }
             // Note: This assumes that the current user can read the profile badge of the
             // originating user.
+            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+            return dpm.getDrawable(
+                    getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION,
+                    this::getDefaultProfileBadgeDrawable);
+        }
+
+        private String getUpdatableProfileBadgeId() {
+            return mContext.getSystemService(UserManager.class).isManagedProfile()
+                    ? WORK_PROFILE_ICON : UNDEFINED;
+        }
+
+        private Drawable getDefaultProfileBadgeDrawable() {
             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
                     new UserHandle(mContext.getUserId()), 0);
         }
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index ec8d989..715de14 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -16,7 +16,11 @@
 
 package android.app;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -27,9 +31,10 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastPrintWriter;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -104,17 +109,21 @@
  * <pre>
  * public class ActivityThread {
  *   ...
+ *   private final PropertyInvalidatedCache.QueryHandler&lt;Integer, Birthday&gt; mBirthdayQuery =
+ *       new PropertyInvalidatedCache.QueryHandler&lt;Integer, Birthday&gt;() {
+ *           {@literal @}Override
+ *           public Birthday apply(Integer) {
+ *              return GetService("birthdayd").getUserBirthday(userId);
+ *           }
+ *       };
  *   private static final int BDAY_CACHE_MAX = 8;  // Maximum birthdays to cache
  *   private static final String BDAY_CACHE_KEY = "cache_key.birthdayd";
  *   private final PropertyInvalidatedCache&lt;Integer, Birthday%&gt; mBirthdayCache = new
- *     PropertyInvalidatedCache&lt;Integer, Birthday%&gt;(BDAY_CACHE_MAX, BDAY_CACHE_KEY) {
- *       {@literal @}Override
- *       protected Birthday recompute(Integer userId) {
- *         return GetService("birthdayd").getUserBirthday(userId);
- *       }
- *     };
+ *     PropertyInvalidatedCache&lt;Integer, Birthday%&gt;(
+ *             BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery);
+ *
  *   public void disableUserBirthdayCache() {
- *     mBirthdayCache.disableLocal();
+ *     mBirthdayCache.disableForCurrentProcess();
  *   }
  *   public void invalidateUserBirthdayCache() {
  *     mBirthdayCache.invalidateCache();
@@ -221,10 +230,124 @@
  *
  * @param <Query> The class used to index cache entries: must be hashable and comparable
  * @param <Result> The class holding cache entries; use a boxed primitive if possible
- *
- * {@hide}
+ * @hide
  */
-public abstract class PropertyInvalidatedCache<Query, Result> {
+@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+@TestApi
+public class PropertyInvalidatedCache<Query, Result> {
+    /**
+     * This is a configuration class that customizes a cache instance.
+     * @hide
+     */
+    @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public static abstract class QueryHandler<Q,R> {
+        /**
+         * Compute a result given a query.  The semantics are those of Functor.
+         */
+        public abstract @Nullable R apply(@NonNull Q query);
+
+        /**
+         * Return true if a query should not use the cache.  The default implementation
+         * always uses the cache.
+         */
+        public boolean shouldBypassCache(@NonNull Q query) {
+            return false;
+        }
+    };
+
+    /**
+     * The system properties used by caches should be of the form <prefix>.<module>.<api>,
+     * where the prefix is "cache_key", the module is one of the constants below, and the
+     * api is any string.  The ability to write the property (which happens during
+     * invalidation) depends on SELinux rules; these rules are defined against
+     * <prefix>.<module>.  Therefore, the module chosen for a cache property must match
+     * the permissions granted to the processes that contain the corresponding caches.
+     * @hide
+     */
+    @IntDef(prefix = { "MODULE_" }, value = {
+                MODULE_TEST,
+                MODULE_SYSTEM,
+                MODULE_BLUETOOTH
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Module {}
+
+    /**
+     * The module used for unit tests and cts tests.  It is expected that no process in
+     * the system has permissions to write properties with this module.
+     * @hide
+     */
+    @TestApi
+    public static final int MODULE_TEST = 0;
+
+    /**
+     * The module used for system server/framework caches.  This is not visible outside
+     * the system processes.
+     * @hide
+     */
+    @TestApi
+    public static final int MODULE_SYSTEM = 1;
+
+    /**
+     * The module used for bluetooth caches.
+     * @hide
+     */
+    @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public static final int MODULE_BLUETOOTH = 2;
+
+    // A static array mapping module constants to strings.
+    private final static String[] sModuleNames =
+            { "test", "system_server", "bluetooth" };
+
+    /**
+     * Construct a system property that matches the rules described above.  The module is
+     * one of the permitted values above.  The API is a string that is a legal Java simple
+     * identifier.  The api is modified to conform to the system property style guide by
+     * replacing every upper case letter with an underscore and the lower case equivalent.
+     * There is no requirement that the apiName be the name of an actual API.
+     *
+     * Be aware that SystemProperties has a maximum length which is private to the
+     * implementation.  The current maximum is 92 characters. If this method creates a
+     * property name that is too long, SystemProperties.set() will fail without a good
+     * error message.
+     * @hide
+     */
+    @TestApi
+    public static @NonNull String createPropertyName(@Module int module, @NonNull String apiName) {
+        char[] api = apiName.toCharArray();
+        int upper = 0;
+        for (int i = 0; i < api.length; i++) {
+            if (Character.isUpperCase(api[i])) {
+                upper++;
+            }
+        }
+        char[] suffix = new char[api.length + upper];
+        int j = 0;
+        for (int i = 0; i < api.length; i++) {
+            if (Character.isJavaIdentifierPart(api[i])) {
+                if (Character.isUpperCase(api[i])) {
+                    suffix[j++] = '_';
+                    suffix[j++] = Character.toLowerCase(api[i]);
+                } else {
+                    suffix[j++] = api[i];
+                }
+            } else {
+                throw new IllegalArgumentException("invalid api name");
+            }
+        }
+
+        String moduleName = null;
+        try {
+            moduleName = sModuleNames[module];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new IllegalArgumentException("invalid module " + module);
+        }
+
+        return "cache_key." + moduleName + "." + new String(suffix);
+    }
+
     /**
      * Reserved nonce values.  Use isReservedNonce() to test for a reserved value.  Note
      * that all values cause the cache to be skipped.
@@ -335,6 +458,25 @@
      */
     private final String mCacheName;
 
+    /**
+     * The function that computes a Result, given a Query.  This function is called on a
+     * cache miss.
+     */
+    private QueryHandler<Query, Result> mComputer;
+
+    /**
+     * A default function that delegates to the deprecated recompute() method.
+     */
+    private static class DefaultComputer<Query, Result> extends QueryHandler<Query, Result> {
+        final PropertyInvalidatedCache<Query, Result> mCache;
+        DefaultComputer(PropertyInvalidatedCache<Query, Result> cache) {
+            mCache = cache;
+        }
+        public Result apply(Query query) {
+            return mCache.recompute(query);
+        }
+    }
+
     @GuardedBy("mLock")
     private final LinkedHashMap<Query, Result> mCache;
 
@@ -359,8 +501,13 @@
      * property name.  New clients should prefer the constructor that takes an explicit
      * cache name.
      *
+     * TODO(216112648): deprecate this as a public interface, in favor of the four-argument
+     * constructor.
+     *
      * @param maxEntries Maximum number of entries to cache; LRU discard
      * @param propertyName Name of the system property holding the cache invalidation nonce.
+     *
+     * @hide
      */
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
         this(maxEntries, propertyName, propertyName);
@@ -369,32 +516,73 @@
     /**
      * Make a new property invalidated cache.
      *
+     * TODO(216112648): deprecate this as a public interface, in favor of the four-argument
+     * constructor.
+     *
      * @param maxEntries Maximum number of entries to cache; LRU discard
      * @param propertyName Name of the system property holding the cache invalidation nonce
      * @param cacheName Name of this cache in debug and dumpsys
+     * @hide
      */
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
             @NonNull String cacheName) {
         mPropertyName = propertyName;
         mCacheName = cacheName;
         mMaxEntries = maxEntries;
-        mCache = new LinkedHashMap<Query, Result>(
+        mComputer = new DefaultComputer<>(this);
+        mCache = createMap();
+        registerCache();
+    }
+
+    /**
+     * Make a new property invalidated cache.  The key is computed from the module and api
+     * parameters.
+     *
+     * @param maxEntries Maximum number of entries to cache; LRU discard
+     * @param module The module under which the cache key should be placed.
+     * @param api The api this cache front-ends.  The api must be a Java identifier but
+     * need not be an actual api.
+     * @param cacheName Name of this cache in debug and dumpsys
+     * @param computer The code to compute values that are not in the cache.
+     * @hide
+     */
+    @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public PropertyInvalidatedCache(int maxEntries, @Module int module, @NonNull String api,
+            @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
+        mPropertyName = createPropertyName(module, api);
+        mCacheName = cacheName;
+        mMaxEntries = maxEntries;
+        mComputer = computer;
+        mCache = createMap();
+        registerCache();
+    }
+
+    // Create a map.  This should be called only from the constructor.
+    private LinkedHashMap<Query, Result> createMap() {
+        return new LinkedHashMap<Query, Result>(
             2 /* start small */,
             0.75f /* default load factor */,
             true /* LRU access order */) {
+                @GuardedBy("mLock")
                 @Override
                 protected boolean removeEldestEntry(Map.Entry eldest) {
                     final int size = size();
                     if (size > mHighWaterMark) {
                         mHighWaterMark = size;
                     }
-                    if (size > maxEntries) {
+                    if (size > mMaxEntries) {
                         mMissOverflow++;
                         return true;
                     }
                     return false;
                 }
-            };
+        };
+    }
+
+    // Register the map in the global list.  If the cache is disabled globally, disable it
+    // now.
+    private void registerCache() {
         synchronized (sCorkLock) {
             sCaches.put(this, null);
             if (sDisabledKeys.contains(mCacheName)) {
@@ -418,8 +606,9 @@
     /**
      * Enable or disable testing.  The testing property map is cleared every time this
      * method is called.
+     * @hide
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @TestApi
     public static void setTestMode(boolean mode) {
         sTesting = mode;
         synchronized (sTestingPropertyMap) {
@@ -431,13 +620,22 @@
      * Enable testing the specific cache key.  Only keys in the map are subject to testing.
      * There is no method to stop testing a property name.  Just disable the test mode.
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public static void testPropertyName(@NonNull String name) {
+    private static void testPropertyName(@NonNull String name) {
         synchronized (sTestingPropertyMap) {
             sTestingPropertyMap.put(name, (long) NONCE_UNSET);
         }
     }
 
+    /**
+     * Enable testing the specific cache key.  Only keys in the map are subject to testing.
+     * There is no method to stop testing a property name.  Just disable the test mode.
+     * @hide
+     */
+    @TestApi
+    public void testPropertyName() {
+        testPropertyName(mPropertyName);
+    }
+
     // Read the system property associated with the current cache.  This method uses the
     // handle for faster reading.
     private long getCurrentNonce() {
@@ -490,6 +688,9 @@
 
     /**
      * Forget all cached values.
+     * TODO(216112648) remove this as a public API.  Clients should invalidate caches, not clear
+     * them.
+     * @hide
      */
     public final void clear() {
         synchronized (mLock) {
@@ -505,22 +706,29 @@
      * Fetch a result from scratch in case it's not in the cache at all.  Called unlocked: may
      * block. If this function returns null, the result of the cache query is null. There is no
      * "negative cache" in the query: we don't cache null results at all.
+     * TODO(216112648): deprecate this as a public interface, in favor of an instance of
+     * QueryHandler.
+     * @hide
      */
-    public abstract @NonNull Result recompute(@NonNull Query query);
+    public Result recompute(@NonNull Query query) {
+        return mComputer.apply(query);
+    }
 
     /**
      * Return true if the query should bypass the cache.  The default behavior is to
      * always use the cache but the method can be overridden for a specific class.
+     * TODO(216112648): deprecate this as a public interface, in favor of an instance of
+     * QueryHandler.
+     * @hide
      */
     public boolean bypass(@NonNull Query query) {
-        return false;
+        return mComputer.shouldBypassCache(query);
     }
 
     /**
-     * Determines if a pair of responses are considered equal. Used to determine whether a
-     * cache is inadvertently returning stale results when VERIFY is set to true.  Some
-     * existing clients override this method, but it is now deprecated in favor of a valid
-     * equals() method on the Result class.
+     * Determines if a pair of responses are considered equal. Used to determine whether
+     * a cache is inadvertently returning stale results when VERIFY is set to true.
+     * @hide
      */
     public boolean resultEquals(Result cachedResult, Result fetchedResult) {
         // If a service crashes and returns a null result, the cached value remains valid.
@@ -541,6 +749,7 @@
      * the meantime (if the nonce has changed in the meantime, we drop the cache and try the
      * whole query again), or 3) null, which causes the old value to be removed from the cache
      * and null to be returned as the result of the cache query.
+     * @hide
      */
     protected Result refresh(Result oldResult, Query query) {
         return oldResult;
@@ -551,7 +760,7 @@
      * testing.  To disable a cache in normal code, use disableLocal().
      * @hide
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @TestApi
     public final void disableInstance() {
         synchronized (mLock) {
             mDisabled = true;
@@ -580,9 +789,10 @@
      * disabled remain disabled (the "disabled" setting is sticky).  However, new caches
      * with this name will not be disabled.  It is not an error if the cache name is not
      * found in the list of disabled caches.
+     * @hide
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public final void clearDisableLocal() {
+    @TestApi
+    public final void forgetDisableLocal() {
         synchronized (sCorkLock) {
             sDisabledKeys.remove(mCacheName);
         }
@@ -592,25 +802,43 @@
      * Disable this cache in the current process, and all other caches that use the same
      * name.  This does not affect caches that have a different name but use the same
      * property.
+     * TODO(216112648) Remove this in favor of disableForCurrentProcess().
+     * @hide
      */
     public final void disableLocal() {
+        disableForCurrentProcess();
+    }
+
+    /**
+     * Disable this cache in the current process, and all other caches that use the same
+     * name.  This does not affect caches that have a different name but use the same
+     * property.
+     * @hide
+     */
+    @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public final void disableForCurrentProcess() {
         disableLocal(mCacheName);
     }
 
     /**
-     * Return whether the cache is disabled in this process.
+     * Return whether a cache instance is disabled.
+     * @hide
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public final boolean isDisabledLocal() {
+    @TestApi
+    public final boolean isDisabled() {
         return mDisabled || !sEnabled;
     }
 
     /**
      * Get a value from the cache or recompute it.
+     * @hide
      */
-    public @NonNull Result query(@NonNull Query query) {
+    @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public final @Nullable Result query(@NonNull Query query) {
         // Let access to mDisabled race: it's atomic anyway.
-        long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED;
+        long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
         if (bypass(query)) {
             currentNonce = NONCE_BYPASS;
         }
@@ -724,8 +952,9 @@
      * When multiple caches share a single property value, using an instance method on one of
      * the cache objects to invalidate all of the cache objects becomes confusing and you should
      * just use the static version of this function.
+     * @hide
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @TestApi
     public final void disableSystemWide() {
         disableSystemWide(mPropertyName);
     }
@@ -746,16 +975,33 @@
     /**
      * Non-static convenience version of invalidateCache() for situations in which only a single
      * PropertyInvalidatedCache is keyed on a particular property value.
+     * @hide
      */
+    @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
     public final void invalidateCache() {
         invalidateCache(mPropertyName);
     }
 
     /**
+     * Invalidate caches in all processes that are keyed for the module and api.
+     * @hide
+     */
+    @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public static void invalidateCache(@Module int module, @NonNull String api) {
+        invalidateCache(createPropertyName(module, api));
+    }
+
+    /**
      * Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
      * {@var name}. This function is synchronous: caches are invalidated upon return.
      *
+     * TODO(216112648) make this method private in favor of the two-argument (module, api)
+     * override.
+     *
      * @param name Name of the cache-key property to invalidate
+     * @hide
      */
     public static void invalidateCache(@NonNull String name) {
         if (!sEnabled) {
@@ -824,6 +1070,7 @@
      * corkInvalidations() and uncorkInvalidations() must be called in pairs.
      *
      * @param name Name of the cache-key property to cork
+     * @hide
      */
     public static void corkInvalidations(@NonNull String name) {
         if (!sEnabled) {
@@ -871,6 +1118,7 @@
      * transitioning it to normal operation (unless explicitly disabled system-wide).
      *
      * @param name Name of the cache-key property to uncork
+     * @hide
      */
     public static void uncorkInvalidations(@NonNull String name) {
         if (!sEnabled) {
@@ -916,6 +1164,7 @@
      * The auto-cork delay is configurable but it should not be too long.  The purpose of
      * the delay is to minimize the number of times a server writes to the system property
      * when invalidating the cache.  One write every 50ms does not hurt system performance.
+     * @hide
      */
     public static final class AutoCorker {
         public static final int DEFAULT_AUTO_CORK_DELAY_MS = 50;
@@ -1043,6 +1292,8 @@
      * Return the query as a string, to be used in debug messages.  New clients should not
      * override this, but should instead add the necessary toString() method to the Query
      * class.
+     * TODO(216112648) add a method in the QueryHandler and deprecate this API.
+     * @hide
      */
     protected @NonNull String queryToString(@NonNull Query query) {
         return Objects.toString(query);
@@ -1054,8 +1305,9 @@
      * process does not have privileges to write SystemProperties. Once disabled it is not
      * possible to re-enable caching in the current process.  If a client wants to
      * temporarily disable caching, use the corking mechanism.
+     * @hide
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @TestApi
     public static void disableForTestMode() {
         Log.d(TAG, "disabling all caches in the process");
         sEnabled = false;
@@ -1064,10 +1316,11 @@
     /**
      * Report the disabled status of this cache instance.  The return value does not
      * reflect status of the property key.
+     * @hide
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @TestApi
     public boolean getDisabledState() {
-        return isDisabledLocal();
+        return isDisabled();
     }
 
     /**
@@ -1133,7 +1386,8 @@
     }
 
     /**
-     * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor.
+     * Dumps contents of every cache in the process to the provided ParcelFileDescriptor.
+     * @hide
      */
     public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
         ArrayList<PropertyInvalidatedCache> activeCaches;
@@ -1174,6 +1428,7 @@
 
     /**
      * Trim memory by clearing all the caches.
+     * @hide
      */
     public static void onTrimMemory() {
         for (PropertyInvalidatedCache pic : getActiveCaches()) {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 56c301f..8fcb07f 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,6 +28,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.drawable.Icon;
+import android.media.MediaRoute2Info;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -39,6 +41,7 @@
 
 import com.android.internal.statusbar.IAddTileResultCallback;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.IUndoMediaTransferCallback;
 import com.android.internal.statusbar.NotificationVisibility;
 
 import java.lang.annotation.Retention;
@@ -338,6 +341,166 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface NavBarModeOverride {}
 
+    /**
+     * State indicating that this sender device is close to a receiver device, so the user can
+     * potentially *start* a cast to the receiver device if the user moves their device a bit
+     * closer.
+     * <p>
+     * Important notes:
+     * <ul>
+     *     <li>This state represents that the device is close enough to inform the user that
+     *     transferring is an option, but the device is *not* close enough to actually initiate a
+     *     transfer yet.</li>
+     *     <li>This state is for *starting* a cast. It should be used when this device is currently
+     *     playing media locally and the media should be transferred to be played on the receiver
+     *     device instead.</li>
+     * </ul>
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST = 0;
+
+    /**
+     * State indicating that this sender device is close to a receiver device, so the user can
+     * potentially *end* a cast on the receiver device if the user moves this device a bit closer.
+     * <p>
+     * Important notes:
+     * <ul>
+     *     <li>This state represents that the device is close enough to inform the user that
+     *     transferring is an option, but the device is *not* close enough to actually initiate a
+     *     transfer yet.</li>
+     *     <li>This state is for *ending* a cast. It should be used when media is currently being
+     *     played on the receiver device and the media should be transferred to play locally
+     *     instead.</li>
+     * </ul>
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST = 1;
+
+    /**
+     * State indicating that a media transfer from this sender device to a receiver device has been
+     * started.
+     * <p>
+     * Important note: This state is for *starting* a cast. It should be used when this device is
+     * currently playing media locally and the media has started being transferred to the receiver
+     * device instead.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED = 2;
+
+    /**
+     * State indicating that a media transfer from the receiver and back to this sender device
+     * has been started.
+     * <p>
+     * Important note: This state is for *ending* a cast. It should be used when media is currently
+     * being played on the receiver device and the media has started being transferred to play
+     * locally instead.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED = 3;
+
+    /**
+     * State indicating that a media transfer from this sender device to a receiver device has
+     * finished successfully.
+     * <p>
+     * Important note: This state is for *starting* a cast. It should be used when this device had
+     * previously been playing media locally and the media has successfully been transferred to the
+     * receiver device instead.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED = 4;
+
+    /**
+     * State indicating that a media transfer from the receiver and back to this sender device has
+     * finished successfully.
+     * <p>
+     * Important note: This state is for *ending* a cast. It should be used when media was
+     * previously being played on the receiver device and has been successfully transferred to play
+     * locally on this device instead.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED = 5;
+
+    /**
+     * State indicating that the attempted transfer to the receiver device has failed.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED = 6;
+
+    /**
+     * State indicating that the attempted transfer back to this device has failed.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED = 7;
+
+    /**
+     * State indicating that this sender device is no longer close to the receiver device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER = 8;
+
+    /** @hide */
+    @IntDef(prefix = {"MEDIA_TRANSFER_SENDER_STATE_"}, value = {
+            MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+            MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+            MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MediaTransferSenderState {}
+
+    /**
+     * State indicating that this receiver device is close to a sender device, so the user can
+     * potentially start or end a cast to the receiver device if the user moves the sender device a
+     * bit closer.
+     * <p>
+     * Important note: This state represents that the device is close enough to inform the user that
+     * transferring is an option, but the device is *not* close enough to actually initiate a
+     * transfer yet.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0;
+
+    /**
+     * State indicating that this receiver device is no longer close to the sender device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1;
+
+    /** @hide */
+    @IntDef(prefix = {"MEDIA_TRANSFER_RECEIVER_STATE_"}, value = {
+            MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+            MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MediaTransferReceiverState {}
+
     @UnsupportedAppUsage
     private Context mContext;
     private IStatusBarService mService;
@@ -789,6 +952,81 @@
         return navBarModeOverride;
     }
 
+    /**
+     * Notifies the system of a new media tap-to-transfer state for the <b>sender</b> device.
+     *
+     * <p>The callback should only be provided for the {@link
+     * MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED} or {@link
+     * MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED} states, since those are the
+     * only states where an action can be un-done.
+     *
+     * @param displayState the new state for media tap-to-transfer.
+     * @param routeInfo the media route information for the media being transferred.
+     * @param undoExecutor an executor to run the callback on and must be provided if the
+     *                     callback is non-null.
+     * @param undoCallback a callback that will be triggered if the user elects to undo a media
+     *                     transfer.
+     *
+     * @throws IllegalArgumentException if an undo callback is provided for states that are not a
+     *   succeeded state.
+     * @throws IllegalArgumentException if an executor is not provided when a callback is.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void updateMediaTapToTransferSenderDisplay(
+            @MediaTransferSenderState int displayState,
+            @NonNull MediaRoute2Info routeInfo,
+            @Nullable Executor undoExecutor,
+            @Nullable Runnable undoCallback
+    ) {
+        Objects.requireNonNull(routeInfo);
+        if (displayState != MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED
+                && displayState != MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED
+                && undoCallback != null) {
+            throw new IllegalArgumentException(
+                    "The undoCallback should only be provided when the state is a "
+                            + "transfer succeeded state");
+        }
+        if (undoCallback != null && undoExecutor == null) {
+            throw new IllegalArgumentException(
+                    "You must pass an executor when you pass an undo callback");
+        }
+        IStatusBarService svc = getService();
+        try {
+            UndoCallback callbackProxy = null;
+            if (undoExecutor != null) {
+                callbackProxy = new UndoCallback(undoExecutor, undoCallback);
+            }
+            svc.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, callbackProxy);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notifies the system of a new media tap-to-transfer state for the <b>receiver</b> device.
+     *
+     * @param displayState the new state for media tap-to-transfer.
+     * @param routeInfo the media route information for the media being transferred.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void updateMediaTapToTransferReceiverDisplay(
+            @MediaTransferReceiverState int displayState,
+            @NonNull MediaRoute2Info routeInfo) {
+        Objects.requireNonNull(routeInfo);
+        IStatusBarService svc = getService();
+        try {
+            svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     public static String windowStateToString(int state) {
         if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";
@@ -1071,4 +1309,29 @@
             mExecutor.execute(() -> mCallback.accept(userResponse));
         }
     }
+
+    /**
+     * @hide
+     */
+    static final class UndoCallback extends IUndoMediaTransferCallback.Stub {
+        @NonNull
+        private final Executor mExecutor;
+        @NonNull
+        private final Runnable mCallback;
+
+        UndoCallback(@NonNull Executor executor, @NonNull Runnable callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onUndoTriggered() {
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(mCallback);
+            } finally {
+                restoreCallingIdentity(callingIdentity);
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index 7c0c08a..f156b30 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -40,7 +40,8 @@
 @DataClass(
         genEqualsHashCode = true,
         genAidl = true,
-        genConstructor = false
+        genConstructor = false,
+        genToString = true
 )
 @DataClass.Suppress({"getOpCode", "getOpMode"})
 public final class SyncNotedAppOp implements Parcelable {
@@ -118,6 +119,10 @@
         return mOpMode;
     }
 
+    private String opCodeToString() {
+        return getOp();
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -153,6 +158,20 @@
 
     @Override
     @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "SyncNotedAppOp { " +
+                "opMode = " + mOpMode + ", " +
+                "opCode = " + opCodeToString() + ", " +
+                "attributionTag = " + mAttributionTag + ", " +
+                "packageName = " + mPackageName +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
     public boolean equals(@Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
         // boolean fieldNameEquals(SyncNotedAppOp other) { ... }
@@ -245,10 +264,10 @@
     };
 
     @DataClass.Generated(
-            time = 1619711733947L,
+            time = 1643320427700L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
-            inputSignatures = "private final  int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic  int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)")
+            inputSignatures = "private final  int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic  int getOpMode()\nprivate  java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index bbdd705..79180cb 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -51,6 +51,8 @@
 import android.app.usage.NetworkStatsManager;
 import android.app.usage.StorageStatsManager;
 import android.app.usage.UsageStatsManager;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+import android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager;
 import android.apphibernation.AppHibernationManager;
 import android.appwidget.AppWidgetManager;
 import android.bluetooth.BluetoothFrameworkInitializer;
@@ -1297,6 +1299,20 @@
                     }
                 });
 
+        registerService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE,
+                WallpaperEffectsGenerationManager.class,
+                new CachedServiceFetcher<WallpaperEffectsGenerationManager>() {
+                    @Override
+                    public WallpaperEffectsGenerationManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getService(
+                                Context.WALLPAPER_EFFECTS_GENERATION_SERVICE);
+                        return b == null ? null :
+                                new WallpaperEffectsGenerationManager(
+                                        IWallpaperEffectsGenerationManager.Stub.asInterface(b));
+                    }
+                });
+
         registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() {
             @Override
             public VrManager createService(ContextImpl ctx) throws ServiceNotFoundException {
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 18f9379..3d2c03d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -214,6 +214,12 @@
     public boolean topActivityInSizeCompat;
 
     /**
+     * Whether the direct top activity is eligible for letterbox education.
+     * @hide
+     */
+    public boolean topActivityEligibleForLetterboxEducation;
+
+    /**
      * Whether this task is resizable. Unlike {@link #resizeMode} (which is what the top activity
      * supports), this is what the system actually uses for resizability based on other policy and
      * developer options.
@@ -398,7 +404,8 @@
 
     /** @hide */
     public boolean hasCompatUI() {
-        return hasCameraCompatControl() || topActivityInSizeCompat;
+        return hasCameraCompatControl() || topActivityInSizeCompat
+                || topActivityEligibleForLetterboxEducation;
     }
 
     /**
@@ -460,6 +467,8 @@
         return displayId == that.displayId
                 && taskId == that.taskId
                 && topActivityInSizeCompat == that.topActivityInSizeCompat
+                && topActivityEligibleForLetterboxEducation
+                    == that.topActivityEligibleForLetterboxEducation
                 && cameraCompatControlState == that.cameraCompatControlState
                 // Bounds are important if top activity has compat controls.
                 && (!hasCompatUI() || configuration.windowConfiguration.getBounds()
@@ -507,6 +516,7 @@
         isVisible = source.readBoolean();
         isSleeping = source.readBoolean();
         topActivityInSizeCompat = source.readBoolean();
+        topActivityEligibleForLetterboxEducation = source.readBoolean();
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
         displayAreaFeatureId = source.readInt();
         cameraCompatControlState = source.readInt();
@@ -551,6 +561,7 @@
         dest.writeBoolean(isVisible);
         dest.writeBoolean(isSleeping);
         dest.writeBoolean(topActivityInSizeCompat);
+        dest.writeBoolean(topActivityEligibleForLetterboxEducation);
         dest.writeTypedObject(mTopActivityLocusId, flags);
         dest.writeInt(displayAreaFeatureId);
         dest.writeInt(cameraCompatControlState);
@@ -585,6 +596,8 @@
                 + " isVisible=" + isVisible
                 + " isSleeping=" + isSleeping
                 + " topActivityInSizeCompat=" + topActivityInSizeCompat
+                + " topActivityEligibleForLetterboxEducation= "
+                        + topActivityEligibleForLetterboxEducation
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
                 + " cameraCompatControlState="
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3960f4e..8326580 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -454,6 +454,52 @@
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
      * </ul>
      *
+     * <p>Once the device admin app is set as the device owner, the following APIs are available for
+     * managing polices on the device:
+     * <ul>
+     * <li>{@link #isDeviceManaged()}</li>
+     * <li>{@link #isUninstallBlocked(ComponentName, String)}</li>
+     * <li>{@link #setUninstallBlocked(ComponentName, String, boolean)}</li>
+     * <li>{@link #setUserControlDisabledPackages(ComponentName, List)}</li>
+     * <li>{@link #getUserControlDisabledPackages(ComponentName)}</li>
+     * <li>{@link #setOrganizationName(ComponentName, CharSequence)}</li>
+     * <li>{@link #setShortSupportMessage(ComponentName, CharSequence)}</li>
+     * <li>{@link #isBackupServiceEnabled(ComponentName)}</li>
+     * <li>{@link #setBackupServiceEnabled(ComponentName, boolean)}</li>
+     * <li>{@link #isLockTaskPermitted(String)}</li>
+     * <li>{@link #setLockTaskFeatures(ComponentName, int)}, where the following lock task features
+     * can be set (otherwise a {@link SecurityException} will be thrown):</li>
+     * <ul>
+     *     <li>{@link #LOCK_TASK_FEATURE_SYSTEM_INFO}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_KEYGUARD}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_HOME}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_NOTIFICATIONS}</li>
+     * </ul>
+     * <li>{@link #setLockTaskPackages(ComponentName, String[])}</li>
+     * <li>{@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}</li>
+     * <li>{@link #clearPackagePersistentPreferredActivities(ComponentName, String)} </li>
+     * <li>{@link #wipeData(int)}</li>
+     * <li>{@link #isDeviceOwnerApp(String)}</li>
+     * <li>{@link #clearDeviceOwnerApp(String)}</li>
+     * <li>{@link #setPermissionGrantState(ComponentName, String, String, int)}, where
+     * {@link permission#READ_PHONE_STATE} is the <b>only</b> permission that can be
+     * {@link #PERMISSION_GRANT_STATE_GRANTED}, {@link #PERMISSION_GRANT_STATE_DENIED}, or
+     * {@link #PERMISSION_GRANT_STATE_DEFAULT} and can <b>only</b> be applied to the device admin
+     * app (otherwise a {@link SecurityException} will be thrown)</li>
+     * <li>{@link #addUserRestriction(ComponentName, String)}, where the following user restrictions
+     * are permitted (otherwise a {@link SecurityException} will be thrown):</li>
+     * <ul>
+     *     <li>{@link UserManager#DISALLOW_ADD_USER}</li>
+     *     <li>{@link UserManager#DISALLOW_DEBUGGING_FEATURES}</li>
+     *     <li>{@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES}</li>
+     *     <li>{@link UserManager#DISALLOW_SAFE_BOOT}</li>
+     *     <li>{@link UserManager#DISALLOW_CONFIG_DATE_TIME}</li>
+     *     <li>{@link UserManager#DISALLOW_OUTGOING_CALLS}</li>
+     * </ul>
+     * <li>{@link #clearUserRestriction(ComponentName, String)}</li>
+     * </ul>
+     *
      * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -587,7 +633,7 @@
      * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} to signal that an update
      * to the role holder is required.
      *
-     * <p>This result code must be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}.
+     * <p>This result code can be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}.
      *
      * @hide
      */
@@ -595,15 +641,19 @@
     public static final int RESULT_UPDATE_ROLE_HOLDER = 2;
 
     /**
-     * A {@link Bundle} extra which describes the state of the role holder at the time when it
-     * returns {@link #RESULT_UPDATE_ROLE_HOLDER}.
+     * A {@link PersistableBundle} extra which the role holder can use to describe its own state
+     * when it returns {@link #RESULT_UPDATE_ROLE_HOLDER}.
      *
-     * <p>After the update completes, the role holder's {@link
-     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link
+     * <p>If {@link #RESULT_UPDATE_ROLE_HOLDER} was accompanied by this extra, after the update
+     * completes, the role holder's {@link #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link
      * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent will be relaunched,
      * which will contain this extra. It is the role holder's responsibility to restore its
      * state from this extra.
      *
+     * <p>The content of this {@link PersistableBundle} is entirely up to the role holder. It
+     * should contain anything the role holder needs to restore its original state when it gets
+     * restarted.
+     *
      * @hide
      */
     @SystemApi
@@ -3506,7 +3556,8 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void acknowledgeNewUserDisclaimer() {
         if (mService != null) {
@@ -3519,6 +3570,25 @@
     }
 
     /**
+     * Checks whether the new managed user disclaimer was viewed by the current user.
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
+    @TestApi
+    public boolean isNewUserDisclaimerAcknowledged() {
+        if (mService != null) {
+            try {
+                return mService.isNewUserDisclaimerAcknowledged();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Return true if the given administrator component is currently active (enabled) in the system.
      *
      * @param admin The administrator component to check for.
@@ -10096,7 +10166,7 @@
      * @hide
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.CREATE_USERS})
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public @UserOperationResult int logoutUser() {
         // TODO(b/214336184): add CTS test
@@ -14577,6 +14647,7 @@
      *
      * @hide
      */
+    @TestApi
     public void setDeviceOwnerType(@NonNull ComponentName admin,
             @DeviceOwnerType int deviceOwnerType) {
         throwIfParentInstance("setDeviceOwnerType");
@@ -14600,6 +14671,7 @@
      *
      * @hide
      */
+    @TestApi
     @DeviceOwnerType
     public int getDeviceOwnerType(@NonNull ComponentName admin) {
         throwIfParentInstance("getDeviceOwnerType");
@@ -14767,6 +14839,11 @@
      * The device may not connect to networks that do not meet the minimum security level.
      * If the current network does not meet the minimum security level set, it will be disconnected.
      *
+     * The following shows the Wi-Fi security levels from the lowest to the highest security level:
+     * {@link #WIFI_SECURITY_OPEN}
+     * {@link #WIFI_SECURITY_PERSONAL}
+     * {@link #WIFI_SECURITY_ENTERPRISE_EAP}
+     * {@link #WIFI_SECURITY_ENTERPRISE_192}
      *
      * @param level minimum security level
      * @throws SecurityException if the caller is not a device owner or a profile owner on
@@ -14943,8 +15020,8 @@
      * <p>Also returns the drawable from {@code defaultDrawableLoader} if
      * {@link DevicePolicyResources.Drawables#UNDEFINED} was passed.
      *
-     * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultDrawableLoader} returned {@code null}.
      *
      * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to
      * set a different value use
@@ -14961,7 +15038,7 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawable(
             @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
             @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -14977,8 +15054,8 @@
      * {@link #getDrawable(String, String, Callable)}
      * if an override was set for that specific source.
      *
-     * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultDrawableLoader} returned {@code null}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -14989,7 +15066,7 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawable(
             @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
             @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15032,8 +15109,8 @@
      * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
      * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
      *
-     * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultDrawableLoader} returned {@code null}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -15046,7 +15123,7 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawableForDensity(
             @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
             @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15064,8 +15141,8 @@
      * Similar to {@link #getDrawable(String, String, String, Callable)}, but also accepts
      * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
      *
-     * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
-     * {@link NullPointerException} is thrown.
+      * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+      * and the call to {@code defaultDrawableLoader} returned {@code null}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -15079,7 +15156,7 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawableForDensity(
             @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
             @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15118,7 +15195,7 @@
     /**
      * For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string
      * resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID
-     * {@code callingPackageResourceId} (see {@link DevicePolicyResources.String}), meaning any
+     * {@code callingPackageResourceId} (see {@link DevicePolicyResources.Strings}), meaning any
      * system UI surface calling {@link #getString} with {@code stringId} will get
      * the new resource after this API is called.
      *
@@ -15154,7 +15231,7 @@
 
     /**
      * Removes the updated strings for the list of {@code stringIds} (see
-     * {@link DevicePolicyResources.String}) that was previously set by calling {@link #setStrings},
+     * {@link DevicePolicyResources.Strings}) that was previously set by calling {@link #setStrings},
      * meaning any subsequent calls to {@link #getString} for the provided IDs will
      * return the default string from {@code defaultStringLoader}.
      *
@@ -15179,14 +15256,14 @@
 
     /**
      * Returns the appropriate updated string for the {@code stringId} (see
-     * {@link DevicePolicyResources.String}) if one was set using
+     * {@link DevicePolicyResources.Strings}) if one was set using
      * {@link #setStrings}, otherwise returns the string from {@code defaultStringLoader}.
      *
      * <p>Also returns the string from {@code defaultStringLoader} if
-     * {@link DevicePolicyResources.String#INVALID_ID} was passed.
+     * {@link DevicePolicyResources.Strings#UNDEFINED} was passed.
      *
-     * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultStringLoader} returned {@code null}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -15201,7 +15278,7 @@
      * @hide
      */
     @SystemApi
-    @NonNull
+    @Nullable
     public String getString(
             @NonNull @DevicePolicyResources.UpdatableStringId String stringId,
             @NonNull Callable<String> defaultStringLoader) {
@@ -15236,8 +15313,8 @@
      * {@link java.util.Formatter} and {@link java.lang.String#format}, (see
      * {@link Resources#getString(int, Object...)}).
      *
-     * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
-     * {@link NullPointerException} is thrown.
+     * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+     * and the call to {@code defaultStringLoader} returned {@code null}.
      *
      * @param stringId The IDs to get the updated resource for.
      * @param defaultStringLoader To get the default string if no updated string was set for
@@ -15247,7 +15324,7 @@
      * @hide
      */
     @SystemApi
-    @NonNull
+    @Nullable
     @SuppressLint("SamShouldBeLast")
     public String getString(
             @NonNull @DevicePolicyResources.UpdatableStringId String stringId,
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 2ad2010..ac39cb4 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -93,6 +93,173 @@
 import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_PERSONAL_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_WORK_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.WORK_PROFILE_PAUSED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.LOCATION_AUTO_GRANTED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.WORK_PROFILE_DEFAULT_APPS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_WORK_ACCOUNT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCOUNTS_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_THIS_DEVICE_ADMIN_APP;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVE_DEVICE_ADMIN_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT_MINIMUM;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_CAMERA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_LOCATION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_MICROPHONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_COUNT_ESTIMATED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_INSTALLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_NONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_CURRENT_INPUT_METHOD;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_DEFAULT_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_HTTP_PROXY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_INPUT_METHOD_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_LOCK_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_APPS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_BUG_REPORT_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_NETWORK_LOGS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_SECURITY_LOGS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_USAGE_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_WORK_DATA_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_WIPE_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_PERSONAL_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_PERMISSIONS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_PERSONAL_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_WORK_AND_PERSONAL_APPS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTACT_YOUR_IT_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_SETTINGS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITHOUT_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITH_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_IT_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENTERPRISE_PRIVACY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ERROR_MOVE_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_UNLOCK_DISABLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_FOR_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.HOW_TO_DISCONNECT_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.IT_ADMIN_POLICY_DISABLING_INFO_URL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_BY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_PROFILE_SETTINGS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGE_DEVICE_ADMIN_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NO_DEVICE_ADMINS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS_NONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ONLY_CONNECT_TRUSTED_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.OTHER_OPTIONS_DISABLED_BY_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PASSWORD_RECENTLY_USED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_PROFILE_APP_SUBTEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PIN_RECENTLY_USED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_AND_UNINSTALL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SELECT_DEVICE_ADMIN_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_POSTSETUP_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PATTERN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARING_REMOTE_BUGREPORT_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.UNINSTALL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.USER_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ALARM_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_APP_SUBTEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PATTERN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_KEYBOARDS_AND_TOOLS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCATION_SWITCH_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCKED_NOTIFICATION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_MANAGED_BY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOT_AVAILABLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_OFF_CONDITION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PASSWORD_REQUIRED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SECURITY_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_OFF_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_ON_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_DETAIL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.YOUR_ACCESS_TO_THIS_DEVICE_TITLE;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
@@ -159,7 +326,8 @@
             Drawables.WORK_PROFILE_OFF_ICON,
             Drawables.WORK_PROFILE_USER_ICON
     })
-    public @interface UpdatableDrawableId {}
+    public @interface UpdatableDrawableId {
+    }
 
     /**
      * Identifiers to specify the desired style for the updatable device management system
@@ -174,7 +342,8 @@
             Drawables.Style.OUTLINE,
             Drawables.Style.DEFAULT
     })
-    public @interface UpdatableDrawableStyle {}
+    public @interface UpdatableDrawableStyle {
+    }
 
     /**
      * Identifiers to specify the location if the updatable device management system resource.
@@ -191,7 +360,8 @@
             Drawables.Source.QUICK_SETTINGS,
             Drawables.Source.STATUS_BAR
     })
-    public @interface UpdatableDrawableSource {}
+    public @interface UpdatableDrawableSource {
+    }
 
     /**
      * Resource identifiers used to update device management-related string resources.
@@ -231,7 +401,7 @@
             PERSONAL_APP_SUSPENSION_TITLE, PERSONAL_APP_SUSPENSION_MESSAGE,
             PERSONAL_APP_SUSPENSION_SOON_MESSAGE, PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE,
             PRINTING_DISABLED_NAMED_ADMIN, LOCATION_CHANGED_TITLE, LOCATION_CHANGED_MESSAGE,
-            NETWORK_LOGGING_TITLE,  NETWORK_LOGGING_MESSAGE,
+            NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE,
             NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, NOTIFICATION_CHANNEL_DEVICE_ADMIN,
             SWITCH_TO_WORK_LABEL, SWITCH_TO_PERSONAL_LABEL, FORWARD_INTENT_TO_WORK,
             FORWARD_INTENT_TO_PERSONAL, RESOLVER_WORK_PROFILE_NOT_SUPPORTED, RESOLVER_PERSONAL_TAB,
@@ -257,7 +427,92 @@
             SWITCH_TO_WORK_MESSAGE, SWITCH_TO_PERSONAL_MESSAGE, BLOCKED_BY_ADMIN_TITLE,
             BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_PERSONAL_MESSAGE,
             BLOCKED_FROM_WORK_MESSAGE, Strings.MediaProvider.WORK_PROFILE_PAUSED_TITLE,
-            WORK_PROFILE_PAUSED_MESSAGE
+            WORK_PROFILE_PAUSED_MESSAGE,
+
+            // Settings Strings
+            FACE_SETTINGS_FOR_WORK_TITLE, WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE,
+            WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK, WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE,
+            WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE,
+            WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE,
+            WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE,
+            WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE, WORK_PROFILE_LOCK_ATTEMPTS_FAILED,
+            ACCESSIBILITY_CATEGORY_WORK, ACCESSIBILITY_CATEGORY_PERSONAL,
+            ACCESSIBILITY_WORK_ACCOUNT_TITLE, ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE,
+            WORK_PROFILE_LOCATION_SWITCH_TITLE, SET_WORK_PROFILE_PASSWORD_HEADER,
+            SET_WORK_PROFILE_PIN_HEADER, SET_WORK_PROFILE_PATTERN_HEADER,
+            CONFIRM_WORK_PROFILE_PASSWORD_HEADER, CONFIRM_WORK_PROFILE_PIN_HEADER,
+            CONFIRM_WORK_PROFILE_PATTERN_HEADER, REENTER_WORK_PROFILE_PASSWORD_HEADER,
+            REENTER_WORK_PROFILE_PIN_HEADER, WORK_PROFILE_CONFIRM_PATTERN, WORK_PROFILE_CONFIRM_PIN,
+            WORK_PROFILE_PASSWORD_REQUIRED, WORK_PROFILE_SECURITY_TITLE,
+            WORK_PROFILE_UNIFY_LOCKS_TITLE, WORK_PROFILE_UNIFY_LOCKS_SUMMARY,
+            WORK_PROFILE_UNIFY_LOCKS_DETAIL, WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT,
+            WORK_PROFILE_KEYBOARDS_AND_TOOLS, WORK_PROFILE_NOT_AVAILABLE, WORK_PROFILE_SETTING,
+            WORK_PROFILE_SETTING_ON_SUMMARY, WORK_PROFILE_SETTING_OFF_SUMMARY, REMOVE_WORK_PROFILE,
+            DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING,
+            WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING, WORK_PROFILE_CONFIRM_REMOVE_TITLE,
+            WORK_PROFILE_CONFIRM_REMOVE_MESSAGE, WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS,
+            WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER, WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE,
+            WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY, WORK_PROFILE_RINGTONE_TITLE,
+            WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE, WORK_PROFILE_ALARM_RINGTONE_TITLE,
+            WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY,
+            ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE,
+            ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE,
+            WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER, WORK_PROFILE_LOCKED_NOTIFICATION_TITLE,
+            WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE,
+            WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY,
+            WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED, CONNECTED_WORK_AND_PERSONAL_APPS_TITLE,
+            CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA, ONLY_CONNECT_TRUSTED_APPS,
+            HOW_TO_DISCONNECT_APPS, CONNECT_APPS_DIALOG_TITLE, CONNECT_APPS_DIALOG_SUMMARY,
+            APP_CAN_ACCESS_PERSONAL_DATA, APP_CAN_ACCESS_PERSONAL_PERMISSIONS,
+            INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT,
+            INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT, WORK_PROFILE_MANAGED_BY, MANAGED_BY,
+            WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING, DISABLED_BY_IT_ADMIN_TITLE,
+            CONTACT_YOUR_IT_ADMIN, WORK_PROFILE_ADMIN_POLICIES_WARNING, USER_ADMIN_POLICIES_WARNING,
+            DEVICE_ADMIN_POLICIES_WARNING, WORK_PROFILE_OFF_CONDITION_TITLE,
+            MANAGED_PROFILE_SETTINGS_TITLE, WORK_PROFILE_CONTACT_SEARCH_TITLE,
+            WORK_PROFILE_CONTACT_SEARCH_SUMMARY, CROSS_PROFILE_CALENDAR_TITLE,
+            CROSS_PROFILE_CALENDAR_SUMMARY, ALWAYS_ON_VPN_PERSONAL_PROFILE, ALWAYS_ON_VPN_DEVICE,
+            ALWAYS_ON_VPN_WORK_PROFILE, CA_CERTS_PERSONAL_PROFILE, CA_CERTS_WORK_PROFILE,
+            CA_CERTS_DEVICE, ADMIN_CAN_LOCK_DEVICE, ADMIN_CAN_WIPE_DEVICE,
+            ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE,
+            ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE, DEVICE_MANAGED_WITHOUT_NAME,
+            DEVICE_MANAGED_WITH_NAME, WORK_PROFILE_APP_SUBTEXT, PERSONAL_PROFILE_APP_SUBTEXT,
+            FINGERPRINT_FOR_WORK, FACE_UNLOCK_DISABLED, FINGERPRINT_UNLOCK_DISABLED,
+            FINGERPRINT_UNLOCK_DISABLED_EXPLANATION, PIN_RECENTLY_USED, PASSWORD_RECENTLY_USED,
+            MANAGE_DEVICE_ADMIN_APPS, NUMBER_OF_DEVICE_ADMINS_NONE, NUMBER_OF_DEVICE_ADMINS,
+            FORGOT_PASSWORD_TITLE, FORGOT_PASSWORD_TEXT, ERROR_MOVE_DEVICE_ADMIN,
+            DEVICE_ADMIN_SETTINGS_TITLE, REMOVE_DEVICE_ADMIN, UNINSTALL_DEVICE_ADMIN,
+            REMOVE_AND_UNINSTALL_DEVICE_ADMIN, SELECT_DEVICE_ADMIN_APPS, NO_DEVICE_ADMINS,
+            ACTIVATE_DEVICE_ADMIN_APP, ACTIVATE_THIS_DEVICE_ADMIN_APP,
+            ACTIVATE_DEVICE_ADMIN_APP_TITLE, NEW_DEVICE_ADMIN_WARNING,
+            NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED, ACTIVE_DEVICE_ADMIN_WARNING,
+            SET_PROFILE_OWNER_TITLE, SET_PROFILE_OWNER_DIALOG_TITLE,
+            SET_PROFILE_OWNER_POSTSETUP_WARNING, OTHER_OPTIONS_DISABLED_BY_ADMIN,
+            REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION, IT_ADMIN_POLICY_DISABLING_INFO_URL,
+            SHARE_REMOTE_BUGREPORT_DIALOG_TITLE, SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT,
+            SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT, SHARING_REMOTE_BUGREPORT_MESSAGE,
+            MANAGED_DEVICE_INFO, MANAGED_DEVICE_INFO_SUMMARY, MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME,
+            ENTERPRISE_PRIVACY_HEADER, INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE,
+            CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE, YOUR_ACCESS_TO_THIS_DEVICE_TITLE,
+            ADMIN_CAN_SEE_WORK_DATA_WARNING, ADMIN_CAN_SEE_APPS_WARNING,
+            ADMIN_CAN_SEE_USAGE_WARNING, ADMIN_CAN_SEE_NETWORK_LOGS_WARNING,
+            ADMIN_CAN_SEE_BUG_REPORT_WARNING, ADMIN_CAN_SEE_SECURITY_LOGS_WARNING,
+            ADMIN_ACTION_NONE, ADMIN_ACTION_APPS_INSTALLED, ADMIN_ACTION_APPS_COUNT_ESTIMATED,
+            ADMIN_ACTIONS_APPS_COUNT_MINIMUM, ADMIN_ACTION_ACCESS_LOCATION,
+            ADMIN_ACTION_ACCESS_MICROPHONE, ADMIN_ACTION_ACCESS_CAMERA,
+            ADMIN_ACTION_SET_DEFAULT_APPS, ADMIN_ACTIONS_APPS_COUNT,
+            ADMIN_ACTION_SET_CURRENT_INPUT_METHOD, ADMIN_ACTION_SET_INPUT_METHOD_NAME,
+            ADMIN_ACTION_SET_HTTP_PROXY, WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY,
+            WORK_PROFILE_PRIVACY_POLICY_INFO, CONNECTED_APPS_SEARCH_KEYWORDS,
+            WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS, ACCOUNTS_SEARCH_KEYWORDS,
+            CONTROLLED_BY_ADMIN_SUMMARY, WORK_PROFILE_USER_LABEL, WORK_CATEGORY_HEADER,
+            PERSONAL_CATEGORY_HEADER,
+
+            // PermissionController Strings
+            WORK_PROFILE_DEFAULT_APPS_TITLE, HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE,
+            BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE, BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE,
+            BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE,
+            LOCATION_AUTO_GRANTED_MESSAGE
     })
     public @interface UpdatableStringId {
     }
@@ -432,7 +687,8 @@
     @SystemApi
     public static final class Strings {
 
-        private Strings() {}
+        private Strings() {
+        }
 
         /**
          * An ID for any string that can't be updated.
@@ -446,23 +702,1221 @@
 
         private static Set<String> buildStringsSet() {
             Set<String> strings = new HashSet<>();
+            strings.addAll(Settings.buildStringsSet());
             strings.addAll(Launcher.buildStringsSet());
             strings.addAll(SystemUi.buildStringsSet());
             strings.addAll(Core.buildStringsSet());
             strings.addAll(DocumentsUi.buildStringsSet());
             strings.addAll(MediaProvider.buildStringsSet());
+            strings.addAll(PermissionController.buildStringsSet());
             return strings;
         }
 
         /**
          * Class containing the identifiers used to update device management-related system strings
+         * in the Settings package
+         *
+         * @hide
+         */
+        public static final class Settings {
+
+            private Settings() {
+            }
+
+            private static final String PREFIX = "Settings.";
+
+            /**
+             * Title shown for menu item that launches face settings or enrollment, for work profile
+             */
+            public static final String FACE_SETTINGS_FOR_WORK_TITLE =
+                    PREFIX + "FACE_SETTINGS_FOR_WORK_TITLE";
+
+            /**
+             * Warning when removing the last fingerprint on a work profile
+             */
+            public static final String WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE =
+                    PREFIX + "WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE";
+
+            /**
+             * Text letting the user know that their IT admin can't reset their screen lock if they
+             * forget it, and they can choose to set another lock that would be specifically for
+             * their work apps
+             */
+            public static final String WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK =
+                    PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK";
+
+            /**
+             * Message shown in screen lock picker for setting up a work profile screen lock
+             */
+            public static final String WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE =
+                    PREFIX + "WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE";
+
+            /**
+             * Title for PreferenceScreen to launch picker for security method for the managed
+             * profile when there is none
+             */
+            public static final String WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE =
+                    PREFIX + "WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE";
+
+            /**
+             * Content of the dialog shown when the user only has one attempt left to provide the
+             * work lock pattern before the work profile is removed
+             */
+            public static final String WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE =
+                    PREFIX + "WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE";
+
+            /**
+             * Content of the dialog shown when the user only has one attempt left to provide the
+             * work lock pattern before the work profile is removed
+             */
+            public static final String WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE =
+                    PREFIX + "WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE";
+
+            /**
+             * Content of the dialog shown when the user only has one attempt left to provide the
+             * work lock pattern before the work profile is removed
+             */
+            public static final String WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE =
+                    PREFIX + "WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE";
+
+            /**
+             * Content of the dialog shown when the user has failed to provide the device lock too
+             * many times and the device is wiped
+             */
+            public static final String WORK_PROFILE_LOCK_ATTEMPTS_FAILED =
+                    PREFIX + "WORK_PROFILE_LOCK_ATTEMPTS_FAILED";
+
+            /**
+             * Content description for work profile accounts group
+             */
+            public static final String ACCESSIBILITY_CATEGORY_WORK =
+                    PREFIX + "ACCESSIBILITY_CATEGORY_WORK";
+
+            /**
+             * Content description for personal profile accounts group
+             */
+            public static final String ACCESSIBILITY_CATEGORY_PERSONAL =
+                    PREFIX + "ACCESSIBILITY_CATEGORY_PERSONAL";
+
+            /**
+             * Content description for work profile details page title
+             */
+            public static final String ACCESSIBILITY_WORK_ACCOUNT_TITLE =
+                    PREFIX + "ACCESSIBILITY_WORK_ACCOUNT_TITLE";
+
+            /**
+             * Content description for personal profile details page title
+             */
+            public static final String ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE =
+                    PREFIX + "ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE";
+
+            /**
+             * Title for work profile location switch
+             */
+            public static final String WORK_PROFILE_LOCATION_SWITCH_TITLE =
+                    PREFIX + "WORK_PROFILE_LOCATION_SWITCH_TITLE";
+
+            /**
+             * Header when setting work profile password
+             */
+            public static final String SET_WORK_PROFILE_PASSWORD_HEADER =
+                    PREFIX + "SET_WORK_PROFILE_PASSWORD_HEADER";
+
+            /**
+             * Header when setting work profile PIN
+             */
+            public static final String SET_WORK_PROFILE_PIN_HEADER =
+                    PREFIX + "SET_WORK_PROFILE_PIN_HEADER";
+
+            /**
+             * Header when setting work profile pattern
+             */
+            public static final String SET_WORK_PROFILE_PATTERN_HEADER =
+                    PREFIX + "SET_WORK_PROFILE_PATTERN_HEADER";
+
+            /**
+             * Header when confirming work profile password
+             */
+            public static final String CONFIRM_WORK_PROFILE_PASSWORD_HEADER =
+                    PREFIX + "CONFIRM_WORK_PROFILE_PASSWORD_HEADER";
+
+            /**
+             * Header when confirming work profile pin
+             */
+            public static final String CONFIRM_WORK_PROFILE_PIN_HEADER =
+                    PREFIX + "CONFIRM_WORK_PROFILE_PIN_HEADER";
+
+            /**
+             * Header when confirming work profile pattern
+             */
+            public static final String CONFIRM_WORK_PROFILE_PATTERN_HEADER =
+                    PREFIX + "CONFIRM_WORK_PROFILE_PATTERN_HEADER";
+
+            /**
+             * Header when re-entering work profile password
+             */
+            public static final String REENTER_WORK_PROFILE_PASSWORD_HEADER =
+                    PREFIX + "REENTER_WORK_PROFILE_PASSWORD_HEADER";
+
+            /**
+             * Header when re-entering work profile pin
+             */
+            public static final String REENTER_WORK_PROFILE_PIN_HEADER =
+                    PREFIX + "REENTER_WORK_PROFILE_PIN_HEADER";
+
+            /**
+             * Message to be used to explain the users that they need to enter their work pattern to
+             * continue a particular operation
+             */
+            public static final String WORK_PROFILE_CONFIRM_PATTERN =
+                    PREFIX + "WORK_PROFILE_CONFIRM_PATTERN";
+
+            /**
+             * Message to be used to explain the users that they need to enter their work pin to
+             * continue a particular operation
+             */
+            public static final String WORK_PROFILE_CONFIRM_PIN =
+                    PREFIX + "WORK_PROFILE_CONFIRM_PIN";
+
+            /**
+             * Message to be used to explain the users that they need to enter their work password
+             * to
+             * continue a particular operation
+             */
+            public static final String WORK_PROFILE_CONFIRM_PASSWORD =
+                    PREFIX + "WORK_PROFILE_CONFIRM_PASSWORD";
+
+            /**
+             * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pattern
+             * that lets them access
+             * their work profile. This is an extra security measure that's required for them to
+             * continue
+             */
+            public static final String WORK_PROFILE_PATTERN_REQUIRED =
+                    PREFIX + "WORK_PROFILE_PATTERN_REQUIRED";
+
+            /**
+             * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pin
+             * that lets them access
+             * their work profile. This is an extra security measure that's required for them to
+             * continue
+             */
+            public static final String WORK_PROFILE_PIN_REQUIRED =
+                    PREFIX + "WORK_PROFILE_PIN_REQUIRED";
+
+            /**
+             * This string shows = PREFIX + "shows"; up on a screen where a user can enter a
+             * password that lets them access
+             * their work profile. This is an extra security measure that's required for them to
+             * continue
+             */
+            public static final String WORK_PROFILE_PASSWORD_REQUIRED =
+                    PREFIX + "WORK_PROFILE_PASSWORD_REQUIRED";
+
+            /**
+             * Header for Work Profile security settings
+             */
+            public static final String WORK_PROFILE_SECURITY_TITLE =
+                    PREFIX + "WORK_PROFILE_SECURITY_TITLE";
+
+            /**
+             * Header for Work Profile unify locks settings
+             */
+            public static final String WORK_PROFILE_UNIFY_LOCKS_TITLE =
+                    PREFIX + "WORK_PROFILE_UNIFY_LOCKS_TITLE";
+
+            /**
+             * Setting option explanation to unify work and personal locks
+             */
+            public static final String WORK_PROFILE_UNIFY_LOCKS_SUMMARY =
+                    PREFIX + "WORK_PROFILE_UNIFY_LOCKS_SUMMARY";
+
+            /**
+             * Further explanation when the user wants to unify work and personal locks
+             */
+            public static final String WORK_PROFILE_UNIFY_LOCKS_DETAIL =
+                    PREFIX + "WORK_PROFILE_UNIFY_LOCKS_DETAIL";
+
+            /**
+             * Ask if the user wants to create a new lock for personal and work as the current work
+             * lock is not enough for the device
+             */
+            public static final String WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT =
+                    PREFIX + "WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT";
+
+            /**
+             * Title of 'Work profile keyboards & tools' preference category
+             */
+            public static final String WORK_PROFILE_KEYBOARDS_AND_TOOLS =
+                    PREFIX + "WORK_PROFILE_KEYBOARDS_AND_TOOLS";
+
+            /**
+             * Label for state when work profile is not available
+             */
+            public static final String WORK_PROFILE_NOT_AVAILABLE =
+                    PREFIX + "WORK_PROFILE_NOT_AVAILABLE";
+
+            /**
+             * Label for work profile setting (to allow turning work profile on and off)
+             */
+            public static final String WORK_PROFILE_SETTING = PREFIX + "WORK_PROFILE_SETTING";
+
+            /**
+             * Description of the work profile setting when the work profile is on
+             */
+            public static final String WORK_PROFILE_SETTING_ON_SUMMARY =
+                    PREFIX + "WORK_PROFILE_SETTING_ON_SUMMARY";
+
+            /**
+             * Description of the work profile setting when the work profile is off
+             */
+            public static final String WORK_PROFILE_SETTING_OFF_SUMMARY =
+                    PREFIX + "WORK_PROFILE_SETTING_OFF_SUMMARY";
+
+            /**
+             * Button text to remove work profile
+             */
+            public static final String REMOVE_WORK_PROFILE = PREFIX + "REMOVE_WORK_PROFILE";
+
+            /**
+             * Text of message to show to device owner user whose administrator has installed a SSL
+             * CA Cert
+             */
+            public static final String DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING =
+                    PREFIX + "DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING";
+
+            /**
+             * Text of message to show to work profile users whose administrator has installed a SSL
+             * CA Cert
+             */
+            public static final String WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING =
+                    PREFIX + "WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING";
+
+            /**
+             * Work profile removal confirmation title
+             */
+            public static final String WORK_PROFILE_CONFIRM_REMOVE_TITLE =
+                    PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_TITLE";
+
+            /**
+             * Work profile removal confirmation message
+             */
+            public static final String WORK_PROFILE_CONFIRM_REMOVE_MESSAGE =
+                    PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_MESSAGE";
+
+            /**
+             * Toast shown when an app in the work profile attempts to open notification settings
+             * and apps in the work profile cannot access notification settings
+             */
+            public static final String WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS =
+                    PREFIX + "WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS";
+
+            /**
+             * Work sound settings section header
+             */
+            public static final String WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER =
+                    PREFIX + "WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER";
+
+            /**
+             * Title for the switch that enables syncing of personal ringtones to work profile
+             */
+            public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE =
+                    PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE";
+
+            /**
+             * Summary for the switch that enables syncing of personal ringtones to work profile
+             */
+            public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY =
+                    PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY";
+
+            /**
+             * Title for the option defining the work profile phone ringtone
+             */
+            public static final String WORK_PROFILE_RINGTONE_TITLE =
+                    PREFIX + "WORK_PROFILE_RINGTONE_TITLE";
+
+            /**
+             * Title for the option defining the default work profile notification ringtone
+             */
+            public static final String WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE =
+                    PREFIX + "WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE";
+
+            /**
+             * Title for the option defining the default work alarm ringtone
+             */
+            public static final String WORK_PROFILE_ALARM_RINGTONE_TITLE =
+                    PREFIX + "WORK_PROFILE_ALARM_RINGTONE_TITLE";
+
+            /**
+             * Summary for sounds when sync with personal sounds is active
+             */
+            public static final String WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY =
+                    PREFIX + "WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY";
+
+            /**
+             * Title for dialog shown when enabling sync with personal sounds
+             */
+            public static final String
+                    ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE =
+                    PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE";
+
+            /**
+             * Message for dialog shown when using the same sounds for work events as for personal
+             * events
+             */
+            public static final String
+                    ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE =
+                    PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE";
+
+            /**
+             * Work profile notifications section header
+             */
+            public static final String WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER =
+                    PREFIX + "WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER";
+
+            /**
+             * Title for the option controlling notifications for work profile
+             */
+            public static final String WORK_PROFILE_LOCKED_NOTIFICATION_TITLE =
+                    PREFIX + "WORK_PROFILE_LOCKED_NOTIFICATION_TITLE";
+
+            /**
+             * Title for redacting sensitive content on lockscreen for work profiles
+             */
+            public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE =
+                    PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE";
+
+            /**
+             * Summary for redacting sensitive content on lockscreen for work profiles
+             */
+            public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY =
+                    PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY";
+
+            /**
+             * Indicates that the work profile admin doesn't allow this notification listener to
+             * access
+             * work profile notifications
+             */
+            public static final String WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED =
+                    PREFIX + "WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED";
+
+            /**
+             * This setting shows a user's connected work and personal apps.
+             */
+            public static final String CONNECTED_WORK_AND_PERSONAL_APPS_TITLE =
+                    PREFIX + "CONNECTED_WORK_AND_PERSONAL_APPS_TITLE";
+
+            /**
+             * This text lets a user know that if they connect work and personal apps,
+             * they will share permissions and can access each other's data
+             */
+            public static final String CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA =
+                    PREFIX + "CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA";
+
+            /**
+             * This text lets a user know that they should only connect work and personal apps if
+             * they
+             * trust the work app with their personal data
+             */
+            public static final String ONLY_CONNECT_TRUSTED_APPS =
+                    PREFIX + "ONLY_CONNECT_TRUSTED_APPS";
+
+            /**
+             * This text lets a user know how to disconnect work and personal apps
+             */
+            public static final String HOW_TO_DISCONNECT_APPS = PREFIX + "HOW_TO_DISCONNECT_APPS";
+
+            /**
+             * Title of confirmation dialog when connecting work and personal apps
+             */
+            public static final String CONNECT_APPS_DIALOG_TITLE =
+                    PREFIX + "CONNECT_APPS_DIALOG_TITLE";
+
+            /**
+             * This dialog is shown when a user tries to connect a work app to a personal
+             * app
+             */
+            public static final String CONNECT_APPS_DIALOG_SUMMARY =
+                    PREFIX + "CONNECT_APPS_DIALOG_SUMMARY";
+
+            /**
+             * This text lets the user know that their work app will be able to access data in their
+             * personal app
+             */
+            public static final String APP_CAN_ACCESS_PERSONAL_DATA =
+                    PREFIX + "APP_CAN_ACCESS_PERSONAL_DATA";
+
+            /**
+             * This text lets the user know that their work app will be able to use permissions in
+             * their personal app
+             */
+            public static final String APP_CAN_ACCESS_PERSONAL_PERMISSIONS =
+                    PREFIX + "APP_CAN_ACCESS_PERSONAL_PERMISSIONS";
+
+            /**
+             * lets a user know that they need to install an app in their work profile in order to
+             * connect it to the corresponding personal app
+             */
+            public static final String INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT =
+                    PREFIX + "INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT";
+
+            /**
+             * lets a user know that they need to install an app in their personal profile in order
+             * to
+             * connect it to the corresponding work app
+             */
+            public static final String INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT =
+                    PREFIX + "INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT";
+
+            /**
+             * Header for showing the organisation managing the work profile
+             */
+            public static final String WORK_PROFILE_MANAGED_BY = PREFIX + "WORK_PROFILE_MANAGED_BY";
+
+            /**
+             * Summary showing the enterprise who manages the device or profile.
+             */
+            public static final String MANAGED_BY = PREFIX + "MANAGED_BY";
+
+            /**
+             * Warning message about disabling usage access on profile owner
+             */
+            public static final String WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING =
+                    PREFIX + "WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING";
+
+            /**
+             * Title for dialog displayed when user taps a setting on their phone that's blocked by
+             * their IT admin
+             */
+            public static final String DISABLED_BY_IT_ADMIN_TITLE =
+                    PREFIX + "DISABLED_BY_IT_ADMIN_TITLE";
+
+            /**
+             * Shown when the user tries to change phone settings that are blocked by their IT admin
+             */
+            public static final String CONTACT_YOUR_IT_ADMIN = PREFIX + "CONTACT_YOUR_IT_ADMIN";
+
+            /**
+             * warn user about policies the admin can set in a work profile
+             */
+            public static final String WORK_PROFILE_ADMIN_POLICIES_WARNING =
+                    PREFIX + "WORK_PROFILE_ADMIN_POLICIES_WARNING";
+
+            /**
+             * warn user about policies the admin can set on a user
+             */
+            public static final String USER_ADMIN_POLICIES_WARNING =
+                    PREFIX + "USER_ADMIN_POLICIES_WARNING";
+
+            /**
+             * warn user about policies the admin can set on a device
+             */
+            public static final String DEVICE_ADMIN_POLICIES_WARNING =
+                    PREFIX + "DEVICE_ADMIN_POLICIES_WARNING";
+
+            /**
+             * Condition that work profile is off
+             */
+            public static final String WORK_PROFILE_OFF_CONDITION_TITLE =
+                    PREFIX + "WORK_PROFILE_OFF_CONDITION_TITLE";
+
+            /**
+             * Title of work profile setting page
+             */
+            public static final String MANAGED_PROFILE_SETTINGS_TITLE =
+                    PREFIX + "MANAGED_PROFILE_SETTINGS_TITLE";
+
+            /**
+             * Setting that lets a user's personal apps identify contacts using the user's work
+             * directory
+             */
+            public static final String WORK_PROFILE_CONTACT_SEARCH_TITLE =
+                    PREFIX + "WORK_PROFILE_CONTACT_SEARCH_TITLE";
+
+            /**
+             * This setting lets a user's personal apps identify contacts using the user's work
+             * directory
+             */
+            public static final String WORK_PROFILE_CONTACT_SEARCH_SUMMARY =
+                    PREFIX + "WORK_PROFILE_CONTACT_SEARCH_SUMMARY";
+
+            /**
+             * This setting lets the user show their work events on their personal calendar
+             */
+            public static final String CROSS_PROFILE_CALENDAR_TITLE =
+                    PREFIX + "CROSS_PROFILE_CALENDAR_TITLE";
+
+            /**
+             * Setting description. If the user turns on this setting, they can see their work
+             * events on their personal calendar
+             */
+            public static final String CROSS_PROFILE_CALENDAR_SUMMARY =
+                    PREFIX + "CROSS_PROFILE_CALENDAR_SUMMARY";
+
+            /**
+             * Label explaining that an always-on VPN was set by the admin in the personal profile
+             */
+            public static final String ALWAYS_ON_VPN_PERSONAL_PROFILE =
+                    PREFIX + "ALWAYS_ON_VPN_PERSONAL_PROFILE";
+
+            /**
+             * Label explaining that an always-on VPN was set by the admin for the entire device
+             */
+            public static final String ALWAYS_ON_VPN_DEVICE = PREFIX + "ALWAYS_ON_VPN_DEVICE";
+
+            /**
+             * Label explaining that an always-on VPN was set by the admin in the work profile
+             */
+            public static final String ALWAYS_ON_VPN_WORK_PROFILE =
+                    PREFIX + "ALWAYS_ON_VPN_WORK_PROFILE";
+
+            /**
+             * Label explaining that the admin installed trusted CA certificates in personal profile
+             */
+            public static final String CA_CERTS_PERSONAL_PROFILE =
+                    PREFIX + "CA_CERTS_PERSONAL_PROFILE";
+
+            /**
+             * Label explaining that the admin installed trusted CA certificates in work profile
+             */
+            public static final String CA_CERTS_WORK_PROFILE = PREFIX + "CA_CERTS_WORK_PROFILE";
+
+            /**
+             * Label explaining that the admin installed trusted CA certificates for the entire
+             * device
+             */
+            public static final String CA_CERTS_DEVICE = PREFIX + "CA_CERTS_DEVICE";
+
+            /**
+             * Label explaining that the admin can lock the device and change the user's password
+             */
+            public static final String ADMIN_CAN_LOCK_DEVICE = PREFIX + "ADMIN_CAN_LOCK_DEVICE";
+
+            /**
+             * Label explaining that the admin can wipe the device remotely
+             */
+            public static final String ADMIN_CAN_WIPE_DEVICE = PREFIX + "ADMIN_CAN_WIPE_DEVICE";
+
+            /**
+             * Label explaining that the admin configured the device to wipe itself when the
+             * password is mistyped too many times
+             */
+            public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE =
+                    PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE";
+
+            /**
+             * Label explaining that the admin configured the work profile to wipe itself when the
+             * password is mistyped too many times
+             */
+            public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE =
+                    PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE";
+
+            /**
+             * Message indicating that the device is enterprise-managed by a Device Owner
+             */
+            public static final String DEVICE_MANAGED_WITHOUT_NAME =
+                    PREFIX + "DEVICE_MANAGED_WITHOUT_NAME";
+
+            /**
+             * Message indicating that the device is enterprise-managed by a Device Owner
+             */
+            public static final String DEVICE_MANAGED_WITH_NAME =
+                    PREFIX + "DEVICE_MANAGED_WITH_NAME";
+
+            /**
+             * Subtext of work profile app for current setting
+             */
+            public static final String WORK_PROFILE_APP_SUBTEXT =
+                    PREFIX + "WORK_PROFILE_APP_SUBTEXT";
+
+            /**
+             * Subtext of personal profile app for current setting
+             */
+            public static final String PERSONAL_PROFILE_APP_SUBTEXT =
+                    PREFIX + "PERSONAL_PROFILE_APP_SUBTEXT";
+
+            /**
+             * Title shown for work menu item that launches fingerprint settings or enrollment
+             */
+            public static final String FINGERPRINT_FOR_WORK = PREFIX + "FINGERPRINT_FOR_WORK";
+
+            /**
+             * Message shown in face enrollment dialog, when face unlock is disabled by device admin
+             */
+            public static final String FACE_UNLOCK_DISABLED = PREFIX + "FACE_UNLOCK_DISABLED";
+
+            /**
+             * message shown in fingerprint enrollment dialog, when fingerprint unlock is disabled
+             * by device admin
+             */
+            public static final String FINGERPRINT_UNLOCK_DISABLED =
+                    PREFIX + "FINGERPRINT_UNLOCK_DISABLED";
+
+            /**
+             * Text shown in fingerprint settings explaining what the fingerprint can be used for in
+             * the case unlocking is disabled
+             */
+            public static final String FINGERPRINT_UNLOCK_DISABLED_EXPLANATION =
+                    PREFIX + "FINGERPRINT_UNLOCK_DISABLED_EXPLANATION";
+
+            /**
+             * Error shown when in PIN mode and PIN has been used recently
+             */
+            public static final String PIN_RECENTLY_USED = PREFIX + "PIN_RECENTLY_USED";
+
+            /**
+             * Error shown when in PASSWORD mode and password has been used recently
+             */
+            public static final String PASSWORD_RECENTLY_USED = PREFIX + "PASSWORD_RECENTLY_USED";
+
+            /**
+             * Title of preference to manage device admin apps
+             */
+            public static final String MANAGE_DEVICE_ADMIN_APPS =
+                    PREFIX + "MANAGE_DEVICE_ADMIN_APPS";
+
+            /**
+             * Inform the user that currently no device admin apps are installed and active
+             */
+            public static final String NUMBER_OF_DEVICE_ADMINS_NONE =
+                    PREFIX + "NUMBER_OF_DEVICE_ADMINS_NONE";
+
+            /**
+             * Inform the user how many device admin apps are installed and active
+             */
+            public static final String NUMBER_OF_DEVICE_ADMINS = PREFIX + "NUMBER_OF_DEVICE_ADMINS";
+
+            /**
+             * Title that asks the user to contact the IT admin to reset password
+             */
+            public static final String FORGOT_PASSWORD_TITLE = PREFIX + "FORGOT_PASSWORD_TITLE";
+
+            /**
+             * Content that asks the user to contact the IT admin to reset password
+             */
+            public static final String FORGOT_PASSWORD_TEXT = PREFIX + "FORGOT_PASSWORD_TEXT";
+
+            /**
+             * Error message shown when trying to move device administrators to external disks, such
+             * as SD card
+             */
+            public static final String ERROR_MOVE_DEVICE_ADMIN = PREFIX + "ERROR_MOVE_DEVICE_ADMIN";
+
+            /**
+             * Device admin app settings title
+             */
+            public static final String DEVICE_ADMIN_SETTINGS_TITLE =
+                    PREFIX + "DEVICE_ADMIN_SETTINGS_TITLE";
+
+            /**
+             * Button to remove the active device admin app
+             */
+            public static final String REMOVE_DEVICE_ADMIN = PREFIX + "REMOVE_DEVICE_ADMIN";
+
+            /**
+             * Button to uninstall the device admin app
+             */
+            public static final String UNINSTALL_DEVICE_ADMIN = PREFIX + "UNINSTALL_DEVICE_ADMIN";
+
+            /**
+             * Button to deactivate and uninstall the device admin app
+             */
+            public static final String REMOVE_AND_UNINSTALL_DEVICE_ADMIN =
+                    PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN";
+
+            /**
+             * Title for selecting device admin apps
+             */
+            public static final String SELECT_DEVICE_ADMIN_APPS =
+                    PREFIX + "SELECT_DEVICE_ADMIN_APPS";
+
+            /**
+             * Message when there are no available device admin apps to display
+             */
+            public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS";
+
+            /**
+             * Title for screen to add a device admin app
+             */
+            public static final String ACTIVATE_DEVICE_ADMIN_APP =
+                    PREFIX + "ACTIVATE_DEVICE_ADMIN_APP";
+
+            /**
+             * Label for button to set the active device admin
+             */
+            public static final String ACTIVATE_THIS_DEVICE_ADMIN_APP =
+                    PREFIX + "ACTIVATE_THIS_DEVICE_ADMIN_APP";
+
+            /**
+             * Activate a specific device admin app title
+             */
+            public static final String ACTIVATE_DEVICE_ADMIN_APP_TITLE =
+                    PREFIX + "ACTIVATE_DEVICE_ADMIN_APP_TITLE";
+
+            /**
+             * Device admin warning message about policies a not active admin can use
+             */
+            public static final String NEW_DEVICE_ADMIN_WARNING =
+                    PREFIX + "NEW_DEVICE_ADMIN_WARNING";
+
+            /**
+             * Simplified device admin warning message
+             */
+            public static final String NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED =
+                    PREFIX + "NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED";
+
+            /**
+             * Device admin warning message about policies the active admin can use
+             */
+            public static final String ACTIVE_DEVICE_ADMIN_WARNING =
+                    PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING";
+
+            /**
+             * Title for screen to set a profile owner
+             */
+            public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE";
+
+            /**
+             * Simplified title for dialog to set a profile owner
+             */
+            public static final String SET_PROFILE_OWNER_DIALOG_TITLE =
+                    PREFIX + "SET_PROFILE_OWNER_DIALOG_TITLE";
+
+            /**
+             * Warning when trying to add a profile owner admin after setup has completed
+             */
+            public static final String SET_PROFILE_OWNER_POSTSETUP_WARNING =
+                    PREFIX + "SET_PROFILE_OWNER_POSTSETUP_WARNING";
+
+            /**
+             * Message displayed to let the user know that some of the options are disabled by admin
+             */
+            public static final String OTHER_OPTIONS_DISABLED_BY_ADMIN =
+                    PREFIX + "OTHER_OPTIONS_DISABLED_BY_ADMIN";
+
+            /**
+             * This is shown if the authenticator for a given account fails to remove it due to
+             * admin restrictions
+             */
+            public static final String REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION =
+                    PREFIX + "REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION";
+
+            /**
+             * Url for learning more about IT admin policy disabling
+             */
+            public static final String IT_ADMIN_POLICY_DISABLING_INFO_URL =
+                    PREFIX + "IT_ADMIN_POLICY_DISABLING_INFO_URL";
+
+            /**
+             * Title of dialog shown to ask for user consent for sharing a bugreport that was
+             * requested
+             * remotely by the IT administrator
+             */
+            public static final String SHARE_REMOTE_BUGREPORT_DIALOG_TITLE =
+                    PREFIX + "SHARE_REMOTE_BUGREPORT_DIALOG_TITLE";
+
+            /**
+             * Message of a dialog shown to ask for user consent for sharing a bugreport that was
+             * requested remotely by the IT administrator
+             */
+            public static final String SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT =
+                    PREFIX + "SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT";
+
+            /**
+             * Message of a dialog shown to ask for user consent for sharing a bugreport that was
+             * requested remotely by the IT administrator and it's still being taken
+             */
+            public static final String SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT =
+                    PREFIX + "SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT";
+
+            /**
+             * Message of a dialog shown to inform that the remote bugreport that was requested
+             * remotely by the IT administrator is still being taken and will be shared when
+             * finished
+             */
+            public static final String SHARING_REMOTE_BUGREPORT_MESSAGE =
+                    PREFIX + "SHARING_REMOTE_BUGREPORT_MESSAGE";
+
+            /**
+             * Managed device information screen title
+             */
+            public static final String MANAGED_DEVICE_INFO = PREFIX + "MANAGED_DEVICE_INFO";
+
+            /**
+             * Summary for managed device info section
+             */
+            public static final String MANAGED_DEVICE_INFO_SUMMARY =
+                    PREFIX + "MANAGED_DEVICE_INFO_SUMMARY";
+
+            /**
+             * Summary for managed device info section including organization name
+             */
+            public static final String MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME =
+                    PREFIX + "MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME";
+
+            /**
+             * Enterprise Privacy settings header, summarizing the powers that the admin has
+             */
+            public static final String ENTERPRISE_PRIVACY_HEADER =
+                    PREFIX + "ENTERPRISE_PRIVACY_HEADER";
+
+            /**
+             * Types of information your organization can see section title
+             */
+            public static final String INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE =
+                    PREFIX + "INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE";
+
+            /**
+             * Changes made by your organization's admin section title
+             */
+            public static final String CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE =
+                    PREFIX + "CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE";
+
+            /**
+             * Your access to this device section title
+             */
+            public static final String YOUR_ACCESS_TO_THIS_DEVICE_TITLE =
+                    PREFIX + "YOUR_ACCESS_TO_THIS_DEVICE_TITLE";
+
+            /**
+             * Things the admin can see: data associated with the work account
+             */
+            public static final String ADMIN_CAN_SEE_WORK_DATA_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_WORK_DATA_WARNING";
+
+            /**
+             * Things the admin can see: Apps installed on the device
+             */
+            public static final String ADMIN_CAN_SEE_APPS_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_APPS_WARNING";
+
+            /**
+             * Things the admin can see: Amount of time and data spent in each app
+             */
+            public static final String ADMIN_CAN_SEE_USAGE_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_USAGE_WARNING";
+
+            /**
+             * Things the admin can see: Most recent network traffic log
+             */
+            public static final String ADMIN_CAN_SEE_NETWORK_LOGS_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_NETWORK_LOGS_WARNING";
+            /**
+             * Things the admin can see: Most recent bug report
+             */
+            public static final String ADMIN_CAN_SEE_BUG_REPORT_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_BUG_REPORT_WARNING";
+
+            /**
+             * Things the admin can see: Security logs
+             */
+            public static final String ADMIN_CAN_SEE_SECURITY_LOGS_WARNING =
+                    PREFIX + "ADMIN_CAN_SEE_SECURITY_LOGS_WARNING";
+
+            /**
+             * Indicate that the admin never took a given action so far (e.g. did not retrieve
+             * security logs or request bug reports).
+             */
+            public static final String ADMIN_ACTION_NONE = PREFIX + "ADMIN_ACTION_NONE";
+
+            /**
+             * Indicate that the admin installed one or more apps on the device
+             */
+            public static final String ADMIN_ACTION_APPS_INSTALLED =
+                    PREFIX + "ADMIN_ACTION_APPS_INSTALLED";
+
+            /**
+             * Explaining that the number of apps is an estimation
+             */
+            public static final String ADMIN_ACTION_APPS_COUNT_ESTIMATED =
+                    PREFIX + "ADMIN_ACTION_APPS_COUNT_ESTIMATED";
+
+            /**
+             * Indicating the minimum number of apps that a label refers to
+             */
+            public static final String ADMIN_ACTIONS_APPS_COUNT_MINIMUM =
+                    PREFIX + "ADMIN_ACTIONS_APPS_COUNT_MINIMUM";
+
+            /**
+             * Indicate that the admin granted one or more apps access to the device's location
+             */
+            public static final String ADMIN_ACTION_ACCESS_LOCATION =
+                    PREFIX + "ADMIN_ACTION_ACCESS_LOCATION";
+
+            /**
+             * Indicate that the admin granted one or more apps access to the microphone
+             */
+            public static final String ADMIN_ACTION_ACCESS_MICROPHONE =
+                    PREFIX + "ADMIN_ACTION_ACCESS_MICROPHONE";
+
+            /**
+             * Indicate that the admin granted one or more apps access to the camera
+             */
+            public static final String ADMIN_ACTION_ACCESS_CAMERA =
+                    PREFIX + "ADMIN_ACTION_ACCESS_CAMERA";
+
+            /**
+             * Indicate that the admin set one or more apps as defaults for common actions
+             */
+            public static final String ADMIN_ACTION_SET_DEFAULT_APPS =
+                    PREFIX + "ADMIN_ACTION_SET_DEFAULT_APPS";
+
+            /**
+             * Indicate the number of apps that a label refers to
+             */
+            public static final String ADMIN_ACTIONS_APPS_COUNT =
+                    PREFIX + "ADMIN_ACTIONS_APPS_COUNT";
+
+            /**
+             * Indicate that the current input method was set by the admin
+             */
+            public static final String ADMIN_ACTION_SET_CURRENT_INPUT_METHOD =
+                    PREFIX + "ADMIN_ACTION_SET_CURRENT_INPUT_METHOD";
+
+            /**
+             * The input method set by the admin
+             */
+            public static final String ADMIN_ACTION_SET_INPUT_METHOD_NAME =
+                    PREFIX + "ADMIN_ACTION_SET_INPUT_METHOD_NAME";
+
+            /**
+             * Indicate that a global HTTP proxy was set by the admin
+             */
+            public static final String ADMIN_ACTION_SET_HTTP_PROXY =
+                    PREFIX + "ADMIN_ACTION_SET_HTTP_PROXY";
+
+            /**
+             * Summary for Enterprise Privacy settings, explaining what the user can expect to find
+             * under it
+             */
+            public static final String WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY =
+                    PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY";
+
+            /**
+             * Setting on privacy settings screen that will show work policy info
+             */
+            public static final String WORK_PROFILE_PRIVACY_POLICY_INFO =
+                    PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO";
+
+            /**
+             * Search keywords for connected work and personal apps
+             */
+            public static final String CONNECTED_APPS_SEARCH_KEYWORDS =
+                    PREFIX + "CONNECTED_APPS_SEARCH_KEYWORDS";
+
+            /**
+             * Work profile unification keywords
+             */
+            public static final String WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS =
+                    PREFIX + "WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS";
+
+            /**
+             * Accounts keywords
+             */
+            public static final String ACCOUNTS_SEARCH_KEYWORDS =
+                    PREFIX + "ACCOUNTS_SEARCH_KEYWORDS";
+
+            /**
+             * Summary for settings preference disabled by administrator
+             */
+            public static final String CONTROLLED_BY_ADMIN_SUMMARY =
+                    PREFIX + "CONTROLLED_BY_ADMIN_SUMMARY";
+
+            /**
+             * User label for a work profile
+             */
+            public static final String WORK_PROFILE_USER_LABEL = PREFIX + "WORK_PROFILE_USER_LABEL";
+
+            /**
+             * Header for items under the work user
+             */
+            public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER";
+
+            /**
+             * Header for items under the personal user
+             */
+            public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "category_personal";
+
+            /**
+             * @hide
+             */
+            static Set<String> buildStringsSet() {
+                Set<String> strings = new HashSet<>();
+                strings.add(FACE_SETTINGS_FOR_WORK_TITLE);
+                strings.add(WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE);
+                strings.add(WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK);
+                strings.add(WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE);
+                strings.add(WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE);
+                strings.add(WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE);
+                strings.add(WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE);
+                strings.add(WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE);
+                strings.add(WORK_PROFILE_LOCK_ATTEMPTS_FAILED);
+                strings.add(ACCESSIBILITY_CATEGORY_WORK);
+                strings.add(ACCESSIBILITY_CATEGORY_PERSONAL);
+                strings.add(ACCESSIBILITY_WORK_ACCOUNT_TITLE);
+                strings.add(ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE);
+                strings.add(WORK_PROFILE_LOCATION_SWITCH_TITLE);
+                strings.add(SET_WORK_PROFILE_PASSWORD_HEADER);
+                strings.add(SET_WORK_PROFILE_PIN_HEADER);
+                strings.add(SET_WORK_PROFILE_PATTERN_HEADER);
+                strings.add(CONFIRM_WORK_PROFILE_PASSWORD_HEADER);
+                strings.add(CONFIRM_WORK_PROFILE_PIN_HEADER);
+                strings.add(CONFIRM_WORK_PROFILE_PATTERN_HEADER);
+                strings.add(REENTER_WORK_PROFILE_PASSWORD_HEADER);
+                strings.add(REENTER_WORK_PROFILE_PIN_HEADER);
+                strings.add(WORK_PROFILE_CONFIRM_PATTERN);
+                strings.add(WORK_PROFILE_CONFIRM_PIN);
+                strings.add(WORK_PROFILE_PASSWORD_REQUIRED);
+                strings.add(WORK_PROFILE_SECURITY_TITLE);
+                strings.add(WORK_PROFILE_UNIFY_LOCKS_TITLE);
+                strings.add(WORK_PROFILE_UNIFY_LOCKS_SUMMARY);
+                strings.add(WORK_PROFILE_UNIFY_LOCKS_DETAIL);
+                strings.add(WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT);
+                strings.add(WORK_PROFILE_KEYBOARDS_AND_TOOLS);
+                strings.add(WORK_PROFILE_NOT_AVAILABLE);
+                strings.add(WORK_PROFILE_SETTING);
+                strings.add(WORK_PROFILE_SETTING_ON_SUMMARY);
+                strings.add(WORK_PROFILE_SETTING_OFF_SUMMARY);
+                strings.add(REMOVE_WORK_PROFILE);
+                strings.add(DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING);
+                strings.add(WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING);
+                strings.add(WORK_PROFILE_CONFIRM_REMOVE_TITLE);
+                strings.add(WORK_PROFILE_CONFIRM_REMOVE_MESSAGE);
+                strings.add(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS);
+                strings.add(WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER);
+                strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE);
+                strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY);
+                strings.add(WORK_PROFILE_RINGTONE_TITLE);
+                strings.add(WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE);
+                strings.add(WORK_PROFILE_ALARM_RINGTONE_TITLE);
+                strings.add(WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY);
+                strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE);
+                strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE);
+                strings.add(WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER);
+                strings.add(WORK_PROFILE_LOCKED_NOTIFICATION_TITLE);
+                strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE);
+                strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY);
+                strings.add(WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED);
+                strings.add(CONNECTED_WORK_AND_PERSONAL_APPS_TITLE);
+                strings.add(CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA);
+                strings.add(ONLY_CONNECT_TRUSTED_APPS);
+                strings.add(HOW_TO_DISCONNECT_APPS);
+                strings.add(CONNECT_APPS_DIALOG_TITLE);
+                strings.add(CONNECT_APPS_DIALOG_SUMMARY);
+                strings.add(APP_CAN_ACCESS_PERSONAL_DATA);
+                strings.add(APP_CAN_ACCESS_PERSONAL_PERMISSIONS);
+                strings.add(INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT);
+                strings.add(INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT);
+                strings.add(WORK_PROFILE_MANAGED_BY);
+                strings.add(MANAGED_BY);
+                strings.add(WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING);
+                strings.add(DISABLED_BY_IT_ADMIN_TITLE);
+                strings.add(CONTACT_YOUR_IT_ADMIN);
+                strings.add(WORK_PROFILE_ADMIN_POLICIES_WARNING);
+                strings.add(USER_ADMIN_POLICIES_WARNING);
+                strings.add(DEVICE_ADMIN_POLICIES_WARNING);
+                strings.add(WORK_PROFILE_OFF_CONDITION_TITLE);
+                strings.add(MANAGED_PROFILE_SETTINGS_TITLE);
+                strings.add(WORK_PROFILE_CONTACT_SEARCH_TITLE);
+                strings.add(WORK_PROFILE_CONTACT_SEARCH_SUMMARY);
+                strings.add(CROSS_PROFILE_CALENDAR_TITLE);
+                strings.add(CROSS_PROFILE_CALENDAR_SUMMARY);
+                strings.add(ALWAYS_ON_VPN_PERSONAL_PROFILE);
+                strings.add(ALWAYS_ON_VPN_DEVICE);
+                strings.add(ALWAYS_ON_VPN_WORK_PROFILE);
+                strings.add(CA_CERTS_PERSONAL_PROFILE);
+                strings.add(CA_CERTS_WORK_PROFILE);
+                strings.add(CA_CERTS_DEVICE);
+                strings.add(ADMIN_CAN_LOCK_DEVICE);
+                strings.add(ADMIN_CAN_WIPE_DEVICE);
+                strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE);
+                strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE);
+                strings.add(DEVICE_MANAGED_WITHOUT_NAME);
+                strings.add(DEVICE_MANAGED_WITH_NAME);
+                strings.add(WORK_PROFILE_APP_SUBTEXT);
+                strings.add(PERSONAL_PROFILE_APP_SUBTEXT);
+                strings.add(FINGERPRINT_FOR_WORK);
+                strings.add(FACE_UNLOCK_DISABLED);
+                strings.add(FINGERPRINT_UNLOCK_DISABLED);
+                strings.add(FINGERPRINT_UNLOCK_DISABLED_EXPLANATION);
+                strings.add(PIN_RECENTLY_USED);
+                strings.add(PASSWORD_RECENTLY_USED);
+                strings.add(MANAGE_DEVICE_ADMIN_APPS);
+                strings.add(NUMBER_OF_DEVICE_ADMINS_NONE);
+                strings.add(NUMBER_OF_DEVICE_ADMINS);
+                strings.add(FORGOT_PASSWORD_TITLE);
+                strings.add(FORGOT_PASSWORD_TEXT);
+                strings.add(ERROR_MOVE_DEVICE_ADMIN);
+                strings.add(DEVICE_ADMIN_SETTINGS_TITLE);
+                strings.add(REMOVE_DEVICE_ADMIN);
+                strings.add(UNINSTALL_DEVICE_ADMIN);
+                strings.add(REMOVE_AND_UNINSTALL_DEVICE_ADMIN);
+                strings.add(SELECT_DEVICE_ADMIN_APPS);
+                strings.add(NO_DEVICE_ADMINS);
+                strings.add(ACTIVATE_DEVICE_ADMIN_APP);
+                strings.add(ACTIVATE_THIS_DEVICE_ADMIN_APP);
+                strings.add(ACTIVATE_DEVICE_ADMIN_APP_TITLE);
+                strings.add(NEW_DEVICE_ADMIN_WARNING);
+                strings.add(NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED);
+                strings.add(ACTIVE_DEVICE_ADMIN_WARNING);
+                strings.add(SET_PROFILE_OWNER_TITLE);
+                strings.add(SET_PROFILE_OWNER_DIALOG_TITLE);
+                strings.add(SET_PROFILE_OWNER_POSTSETUP_WARNING);
+                strings.add(OTHER_OPTIONS_DISABLED_BY_ADMIN);
+                strings.add(REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION);
+                strings.add(IT_ADMIN_POLICY_DISABLING_INFO_URL);
+                strings.add(SHARE_REMOTE_BUGREPORT_DIALOG_TITLE);
+                strings.add(SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT);
+                strings.add(SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT);
+                strings.add(SHARING_REMOTE_BUGREPORT_MESSAGE);
+                strings.add(MANAGED_DEVICE_INFO);
+                strings.add(MANAGED_DEVICE_INFO_SUMMARY);
+                strings.add(MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME);
+                strings.add(ENTERPRISE_PRIVACY_HEADER);
+                strings.add(INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE);
+                strings.add(CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE);
+                strings.add(YOUR_ACCESS_TO_THIS_DEVICE_TITLE);
+                strings.add(ADMIN_CAN_SEE_WORK_DATA_WARNING);
+                strings.add(ADMIN_CAN_SEE_APPS_WARNING);
+                strings.add(ADMIN_CAN_SEE_USAGE_WARNING);
+                strings.add(ADMIN_CAN_SEE_NETWORK_LOGS_WARNING);
+                strings.add(ADMIN_CAN_SEE_BUG_REPORT_WARNING);
+                strings.add(ADMIN_CAN_SEE_SECURITY_LOGS_WARNING);
+                strings.add(ADMIN_ACTION_NONE);
+                strings.add(ADMIN_ACTION_APPS_INSTALLED);
+                strings.add(ADMIN_ACTION_APPS_COUNT_ESTIMATED);
+                strings.add(ADMIN_ACTIONS_APPS_COUNT_MINIMUM);
+                strings.add(ADMIN_ACTION_ACCESS_LOCATION);
+                strings.add(ADMIN_ACTION_ACCESS_MICROPHONE);
+                strings.add(ADMIN_ACTION_ACCESS_CAMERA);
+                strings.add(ADMIN_ACTION_SET_DEFAULT_APPS);
+                strings.add(ADMIN_ACTIONS_APPS_COUNT);
+                strings.add(ADMIN_ACTION_SET_CURRENT_INPUT_METHOD);
+                strings.add(ADMIN_ACTION_SET_INPUT_METHOD_NAME);
+                strings.add(ADMIN_ACTION_SET_HTTP_PROXY);
+                strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY);
+                strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO);
+                strings.add(CONNECTED_APPS_SEARCH_KEYWORDS);
+                strings.add(WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS);
+                strings.add(ACCOUNTS_SEARCH_KEYWORDS);
+                strings.add(CONTROLLED_BY_ADMIN_SUMMARY);
+                strings.add(WORK_PROFILE_USER_LABEL);
+                strings.add(WORK_CATEGORY_HEADER);
+                strings.add(PERSONAL_CATEGORY_HEADER);
+                return strings;
+            }
+        }
+
+        /**
+         * Class containing the identifiers used to update device management-related system strings
          * in the Launcher package.
          *
          * @hide
          */
         public static final class Launcher {
 
-            private Launcher(){}
+            private Launcher() {
+            }
 
             private static final String PREFIX = "Launcher.";
 
@@ -576,6 +2030,7 @@
 
             private SystemUi() {
             }
+
             private static final String PREFIX = "SystemUi.";
 
             /**
@@ -649,9 +2104,9 @@
                     PREFIX + "QS_MSG_NAMED_WORK_PROFILE_MONITORING";
 
             /**
-            * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
+             * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
              * admin.
-            */
+             */
             public static final String QS_MSG_WORK_PROFILE_NETWORK =
                     PREFIX + "QS_MSG_WORK_PROFILE_NETWORK";
 
@@ -1413,5 +2868,71 @@
                 return strings;
             }
         }
+
+        /**
+         * Class containing the identifiers used to update device management-related system strings
+         * in the PermissionController module.
+         */
+        public static final class PermissionController {
+
+            private PermissionController() {
+            }
+
+            private static final String PREFIX = "PermissionController.";
+
+            /**
+             * Title for settings page to show default apps for work.
+             */
+            public static final String WORK_PROFILE_DEFAULT_APPS_TITLE =
+                    PREFIX + "WORK_PROFILE_DEFAULT_APPS_TITLE";
+
+            /**
+             * Summary indicating that a home role holder app is missing work profile support.
+             */
+            public static final String HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE =
+                    PREFIX + "HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE";
+
+            /**
+             * Summary of a permission switch in Settings when the background access is denied by an
+             * admin.
+             */
+            public static final String BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE =
+                    PREFIX + "BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE";
+
+            /**
+             * Summary of a permission switch in Settings when the background access is enabled by
+             * an admin.
+             */
+            public static final String BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE =
+                    PREFIX + "BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+
+            /**
+             * Summary of a permission switch in Settings when the foreground access is enabled by
+             * an admin.
+             */
+            public static final String FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE =
+                    PREFIX + "FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+
+            /**
+             * Body of the notification shown to notify the user that the location permission has
+             * been granted to an app, accepts app name as a param.
+             */
+            public static final String LOCATION_AUTO_GRANTED_MESSAGE =
+                    PREFIX + "LOCATION_AUTO_GRANTED_MESSAGE";
+
+            /**
+             * @hide
+             */
+            static Set<String> buildStringsSet() {
+                Set<String> strings = new HashSet<>();
+                strings.add(WORK_PROFILE_DEFAULT_APPS_TITLE);
+                strings.add(HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE);
+                strings.add(BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE);
+                strings.add(BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE);
+                strings.add(FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE);
+                strings.add(LOCATION_AUTO_GRANTED_MESSAGE);
+                return strings;
+            }
+        }
     }
 }
diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 8c232c0..1f7ae4a 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.stats.devicepolicy.DevicePolicyEnums;
 
 import java.util.Locale;
@@ -52,6 +53,7 @@
     @SuppressLint("UseIcu")
     @Nullable private final Locale mLocale;
     private final boolean mDeviceOwnerCanGrantSensorsPermissions;
+    @NonNull private final PersistableBundle mAdminExtras;
 
     private FullyManagedDeviceProvisioningParams(
             @NonNull ComponentName deviceAdminComponentName,
@@ -60,7 +62,8 @@
             @Nullable String timeZone,
             long localTime,
             @Nullable @SuppressLint("UseIcu") Locale locale,
-            boolean deviceOwnerCanGrantSensorsPermissions) {
+            boolean deviceOwnerCanGrantSensorsPermissions,
+            @NonNull PersistableBundle adminExtras) {
         this.mDeviceAdminComponentName = requireNonNull(deviceAdminComponentName);
         this.mOwnerName = requireNonNull(ownerName);
         this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
@@ -69,6 +72,7 @@
         this.mLocale = locale;
         this.mDeviceOwnerCanGrantSensorsPermissions =
                 deviceOwnerCanGrantSensorsPermissions;
+        this.mAdminExtras = adminExtras;
     }
 
     private FullyManagedDeviceProvisioningParams(
@@ -78,14 +82,16 @@
             @Nullable String timeZone,
             long localTime,
             @Nullable String localeStr,
-            boolean deviceOwnerCanGrantSensorsPermissions) {
+            boolean deviceOwnerCanGrantSensorsPermissions,
+            @Nullable PersistableBundle adminExtras) {
         this(deviceAdminComponentName,
                 ownerName,
                 leaveAllSystemAppsEnabled,
                 timeZone,
                 localTime,
                 getLocale(localeStr),
-                deviceOwnerCanGrantSensorsPermissions);
+                deviceOwnerCanGrantSensorsPermissions,
+                adminExtras);
     }
 
     @Nullable
@@ -151,6 +157,15 @@
     }
 
     /**
+     * Returns a copy of the admin extras bundle.
+     *
+     * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+     */
+    public @NonNull PersistableBundle getAdminExtras() {
+        return new PersistableBundle(mAdminExtras);
+    }
+
+    /**
      * Logs the provisioning params using {@link DevicePolicyEventLogger}.
      *
      * @hide
@@ -188,6 +203,7 @@
         @Nullable private Locale mLocale;
         // Default to allowing control over sensor permission grants.
         boolean mDeviceOwnerCanGrantSensorsPermissions = true;
+        @NonNull private PersistableBundle mAdminExtras;
 
         /**
          * Initialize a new {@link Builder} to construct a
@@ -262,6 +278,17 @@
         }
 
         /**
+         * Sets a {@link PersistableBundle} that contains admin-specific extras.
+         */
+        @NonNull
+        public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) {
+            mAdminExtras = adminExtras != null
+                    ? new PersistableBundle(adminExtras)
+                    : new PersistableBundle();
+            return this;
+        }
+
+        /**
          * Combines all of the attributes that have been set on this {@code Builder}
          *
          * @return a new {@link FullyManagedDeviceProvisioningParams} object.
@@ -275,7 +302,8 @@
                     mTimeZone,
                     mLocalTime,
                     mLocale,
-                    mDeviceOwnerCanGrantSensorsPermissions);
+                    mDeviceOwnerCanGrantSensorsPermissions,
+                    mAdminExtras);
         }
     }
 
@@ -298,6 +326,7 @@
                 + ", mLocale=" + (mLocale == null ? "null" : mLocale)
                 + ", mDeviceOwnerCanGrantSensorsPermissions="
                 + mDeviceOwnerCanGrantSensorsPermissions
+                + ", mAdminExtras=" + mAdminExtras
                 + '}';
     }
 
@@ -310,6 +339,7 @@
         dest.writeLong(mLocalTime);
         dest.writeString(mLocale == null ? null : mLocale.toLanguageTag());
         dest.writeBoolean(mDeviceOwnerCanGrantSensorsPermissions);
+        dest.writePersistableBundle(mAdminExtras);
     }
 
     @NonNull
@@ -324,6 +354,7 @@
                     long localtime = in.readLong();
                     String locale = in.readString();
                     boolean deviceOwnerCanGrantSensorsPermissions = in.readBoolean();
+                    PersistableBundle adminExtras = in.readPersistableBundle();
 
                     return new FullyManagedDeviceProvisioningParams(
                             componentName,
@@ -332,7 +363,8 @@
                             timeZone,
                             localtime,
                             locale,
-                            deviceOwnerCanGrantSensorsPermissions);
+                            deviceOwnerCanGrantSensorsPermissions,
+                            adminExtras);
                 }
 
                 @Override
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 927ee0c..a7a51f8 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -272,6 +272,7 @@
     int getLogoutUserId();
     List<UserHandle> getSecondaryUsers(in ComponentName who);
     void acknowledgeNewUserDisclaimer();
+    boolean isNewUserDisclaimerAcknowledged();
 
     void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName);
     int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
index ccbef73..f91d60a 100644
--- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java
+++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
@@ -23,8 +23,10 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.stats.devicepolicy.DevicePolicyEnums;
 
 /**
@@ -49,7 +51,7 @@
     private final boolean mLeaveAllSystemAppsEnabled;
     private final boolean mOrganizationOwnedProvisioning;
     private final boolean mKeepAccountOnMigration;
-
+    @NonNull private final PersistableBundle mAdminExtras;
 
     private ManagedProfileProvisioningParams(
             @NonNull ComponentName profileAdminComponentName,
@@ -58,7 +60,8 @@
             @Nullable Account accountToMigrate,
             boolean leaveAllSystemAppsEnabled,
             boolean organizationOwnedProvisioning,
-            boolean keepAccountOnMigration) {
+            boolean keepAccountOnMigration,
+            @NonNull PersistableBundle adminExtras) {
         this.mProfileAdminComponentName = requireNonNull(profileAdminComponentName);
         this.mOwnerName = requireNonNull(ownerName);
         this.mProfileName = profileName;
@@ -66,6 +69,7 @@
         this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
         this.mOrganizationOwnedProvisioning = organizationOwnedProvisioning;
         this.mKeepAccountOnMigration = keepAccountOnMigration;
+        this.mAdminExtras = adminExtras;
     }
 
     /**
@@ -124,6 +128,15 @@
     }
 
     /**
+     * Returns a copy of the admin extras bundle.
+     *
+     * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+     */
+    public @NonNull PersistableBundle getAdminExtras() {
+        return new PersistableBundle(mAdminExtras);
+    }
+
+    /**
      * Logs the provisioning params using {@link DevicePolicyEventLogger}.
      *
      * @hide
@@ -160,6 +173,7 @@
         private boolean mLeaveAllSystemAppsEnabled;
         private boolean mOrganizationOwnedProvisioning;
         private boolean mKeepingAccountOnMigration;
+        @Nullable private PersistableBundle mAdminExtras;
 
         /**
          * Initialize a new {@link Builder) to construct a {@link ManagedProfileProvisioningParams}.
@@ -235,6 +249,17 @@
         }
 
         /**
+         * Sets a {@link Bundle} that contains admin-specific extras.
+         */
+        @NonNull
+        public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) {
+            mAdminExtras = adminExtras != null
+                    ? new PersistableBundle(adminExtras)
+                    : new PersistableBundle();
+            return this;
+        }
+
+        /**
          * Combines all of the attributes that have been set on this {@code Builder}.
          *
          * @return a new {@link ManagedProfileProvisioningParams} object.
@@ -248,7 +273,8 @@
                     mAccountToMigrate,
                     mLeaveAllSystemAppsEnabled,
                     mOrganizationOwnedProvisioning,
-                    mKeepingAccountOnMigration);
+                    mKeepingAccountOnMigration,
+                    mAdminExtras);
         }
     }
 
@@ -270,6 +296,7 @@
                 + ", mLeaveAllSystemAppsEnabled=" + mLeaveAllSystemAppsEnabled
                 + ", mOrganizationOwnedProvisioning=" + mOrganizationOwnedProvisioning
                 + ", mKeepAccountOnMigration=" + mKeepAccountOnMigration
+                + ", mAdminExtras=" + mAdminExtras
                 + '}';
     }
 
@@ -282,6 +309,7 @@
         dest.writeBoolean(mLeaveAllSystemAppsEnabled);
         dest.writeBoolean(mOrganizationOwnedProvisioning);
         dest.writeBoolean(mKeepAccountOnMigration);
+        dest.writePersistableBundle(mAdminExtras);
     }
 
     public static final @NonNull Creator<ManagedProfileProvisioningParams> CREATOR =
@@ -295,6 +323,7 @@
                     boolean leaveAllSystemAppsEnabled = in.readBoolean();
                     boolean organizationOwnedProvisioning = in.readBoolean();
                     boolean keepAccountMigrated = in.readBoolean();
+                    PersistableBundle adminExtras = in.readPersistableBundle();
 
                     return new ManagedProfileProvisioningParams(
                             componentName,
@@ -303,7 +332,8 @@
                             account,
                             leaveAllSystemAppsEnabled,
                             organizationOwnedProvisioning,
-                            keepAccountMigrated);
+                            keepAccountMigrated,
+                            adminExtras);
                 }
 
                 @Override
diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java
index dba3628..0b1b166 100644
--- a/core/java/android/app/admin/ParcelableResource.java
+++ b/core/java/android/app/admin/ParcelableResource.java
@@ -175,7 +175,7 @@
      * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated
      * drawable was not found or could not be loaded.</p>
      */
-    @NonNull
+    @Nullable
     public Drawable getDrawable(
             Context context,
             int density,
@@ -200,7 +200,7 @@
      * <p>Returns the default string by calling  {@code defaultStringLoader} if the updated
      * string was not found or could not be loaded.</p>
      */
-    @NonNull
+    @Nullable
     public String getString(
             Context context,
             @NonNull Callable<String> defaultStringLoader) {
@@ -267,17 +267,11 @@
     /**
      * returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}.
      */
-    @NonNull
+    @Nullable
     public static Drawable loadDefaultDrawable(@NonNull Callable<Drawable> defaultDrawableLoader) {
         try {
             Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
-
-            Drawable drawable = defaultDrawableLoader.call();
-            Objects.requireNonNull(drawable, "defaultDrawable can't be null");
-
-            return drawable;
-        } catch (NullPointerException rethrown) {
-            throw rethrown;
+            return defaultDrawableLoader.call();
         } catch (Exception e) {
             throw new RuntimeException("Couldn't load default drawable: ", e);
         }
@@ -286,17 +280,11 @@
     /**
      * returns the {@link String} loaded from calling {@code defaultStringLoader}.
      */
-    @NonNull
+    @Nullable
     public static String loadDefaultString(@NonNull Callable<String> defaultStringLoader) {
         try {
             Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null");
-
-            String string = defaultStringLoader.call();
-            Objects.requireNonNull(string, "defaultString can't be null");
-
-            return string;
-        } catch (NullPointerException rethrown) {
-            throw rethrown;
+            return defaultStringLoader.call();
         } catch (Exception e) {
             throw new RuntimeException("Couldn't load default string: ", e);
         }
diff --git a/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java b/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
new file mode 100644
index 0000000..dfbc7a4
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wallpapereffectsgeneration;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Representing the position and other parameters of camera of a single frame.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CameraAttributes implements Parcelable {
+    /**
+     * The location of the anchor within the 3D scene.
+     * Expecting 3 floats representing the x, y, z coordinates
+     * of the anchor point.
+     */
+    @NonNull
+    private float[] mAnchorPointInWorldSpace;
+    /**
+     * Where the anchor point should project to in the output image.
+     * Expecting 2 floats representing the u,v coordinates of the
+     * anchor point.
+     */
+    @NonNull
+    private float[] mAnchorPointInOutputUvSpace;
+    /**
+     * Specifies the amount of yaw orbit rotation the camera should perform
+     * around the anchor point in world space.
+     */
+    private float mCameraOrbitYawDegrees;
+    /**
+     * Specifies the amount of pitch orbit rotation the camera should perform
+     * around the anchor point in world space.
+     */
+    private float mCameraOrbitPitchDegrees;
+    /**
+     * Specifies by how much the camera should be placed towards the anchor
+     * point in world space, which is also called dolly distance.
+     */
+    private float mDollyDistanceInWorldSpace;
+    /**
+     * Specifies the vertical fov degrees of the virtual image.
+     */
+    private float mVerticalFovDegrees;
+    /**
+     * The frustum of near plane.
+     */
+    private float mFrustumNearInWorldSpace;
+    /**
+     * The frustum of far plane.
+     */
+    private float mFrustumFarInWorldSpace;
+
+    private CameraAttributes(Parcel in) {
+        this.mCameraOrbitYawDegrees = in.readFloat();
+        this.mCameraOrbitPitchDegrees = in.readFloat();
+        this.mDollyDistanceInWorldSpace = in.readFloat();
+        this.mVerticalFovDegrees = in.readFloat();
+        this.mFrustumNearInWorldSpace = in.readFloat();
+        this.mFrustumFarInWorldSpace = in.readFloat();
+        this.mAnchorPointInWorldSpace = in.createFloatArray();
+        this.mAnchorPointInOutputUvSpace = in.createFloatArray();
+    }
+
+    private CameraAttributes(float[] anchorPointInWorldSpace, float[] anchorPointInOutputUvSpace,
+            float cameraOrbitYawDegrees, float cameraOrbitPitchDegrees,
+            float dollyDistanceInWorldSpace,
+            float verticalFovDegrees, float frustumNearInWorldSpace, float frustumFarInWorldSpace) {
+        mAnchorPointInWorldSpace = anchorPointInWorldSpace;
+        mAnchorPointInOutputUvSpace = anchorPointInOutputUvSpace;
+        mCameraOrbitYawDegrees = cameraOrbitYawDegrees;
+        mCameraOrbitPitchDegrees = cameraOrbitPitchDegrees;
+        mDollyDistanceInWorldSpace = dollyDistanceInWorldSpace;
+        mVerticalFovDegrees = verticalFovDegrees;
+        mFrustumNearInWorldSpace = frustumNearInWorldSpace;
+        mFrustumFarInWorldSpace = frustumFarInWorldSpace;
+    }
+
+    /**
+     * Get the location of the anchor within the 3D scene. The response float array contains
+     * 3 floats representing the x, y, z coordinates
+     */
+    @NonNull
+    public float[] getAnchorPointInWorldSpace() {
+        return mAnchorPointInWorldSpace;
+    }
+
+    /**
+     * Get where the anchor point should project to in the output image. The response float
+     * array contains 2 floats representing the u,v coordinates of the anchor point.
+     */
+    @NonNull
+    public float[] getAnchorPointInOutputUvSpace() {
+        return mAnchorPointInOutputUvSpace;
+    }
+
+    /**
+     * Get the camera yaw orbit rotation.
+     */
+    public float getCameraOrbitYawDegrees() {
+        return mCameraOrbitYawDegrees;
+    }
+
+    /**
+     * Get the camera pitch orbit rotation.
+     */
+    public float getCameraOrbitPitchDegrees() {
+        return mCameraOrbitPitchDegrees;
+    }
+
+    /**
+     * Get how many units the camera should be placed towards the anchor point in world space.
+     */
+    public float getDollyDistanceInWorldSpace() {
+        return mDollyDistanceInWorldSpace;
+    }
+
+    /**
+     * Get the camera vertical fov degrees.
+     */
+    public float getVerticalFovDegrees() {
+        return mVerticalFovDegrees;
+    }
+
+    /**
+     * Get the frustum in near plane.
+     */
+    public float getFrustumNearInWorldSpace() {
+        return mFrustumNearInWorldSpace;
+    }
+
+    /**
+     * Get the frustum in far plane.
+     */
+    public float getFrustumFarInWorldSpace() {
+        return mFrustumFarInWorldSpace;
+    }
+
+    @NonNull
+    public static final Creator<CameraAttributes> CREATOR = new Creator<CameraAttributes>() {
+        @Override
+        public CameraAttributes createFromParcel(Parcel in) {
+            return new CameraAttributes(in);
+        }
+
+        @Override
+        public CameraAttributes[] newArray(int size) {
+            return new CameraAttributes[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeFloat(mCameraOrbitYawDegrees);
+        out.writeFloat(mCameraOrbitPitchDegrees);
+        out.writeFloat(mDollyDistanceInWorldSpace);
+        out.writeFloat(mVerticalFovDegrees);
+        out.writeFloat(mFrustumNearInWorldSpace);
+        out.writeFloat(mFrustumFarInWorldSpace);
+        out.writeFloatArray(mAnchorPointInWorldSpace);
+        out.writeFloatArray(mAnchorPointInOutputUvSpace);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Builder for {@link CameraAttributes}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        @NonNull
+        private float[] mAnchorPointInWorldSpace;
+        @NonNull
+        private float[] mAnchorPointInOutputUvSpace;
+        private float mCameraOrbitYawDegrees;
+        private float mCameraOrbitPitchDegrees;
+        private float mDollyDistanceInWorldSpace;
+        private float mVerticalFovDegrees;
+        private float mFrustumNearInWorldSpace;
+        private float mFrustumFarInWorldSpace;
+
+        /**
+         * Constructor with anchor point in world space and anchor point in output image
+         * space.
+         *
+         * @param anchorPointInWorldSpace the location of the anchor within the 3D scene. The
+         *  float array contains 3 floats representing the x, y, z coordinates.
+         * @param anchorPointInOutputUvSpace where the anchor point should project to in the
+         *  output image. The  float array contains 2 floats representing the u,v coordinates
+         *  of the anchor point.
+         *
+         * @hide
+         */
+        @SystemApi
+        public Builder(@NonNull float[] anchorPointInWorldSpace,
+                @NonNull float[] anchorPointInOutputUvSpace) {
+            mAnchorPointInWorldSpace = anchorPointInWorldSpace;
+            mAnchorPointInOutputUvSpace = anchorPointInOutputUvSpace;
+        }
+
+        /**
+         * Sets the camera orbit yaw rotation.
+         */
+        @NonNull
+        public Builder setCameraOrbitYawDegrees(
+                @FloatRange(from = -180.0f, to = 180.0f) float cameraOrbitYawDegrees) {
+            mCameraOrbitYawDegrees = cameraOrbitYawDegrees;
+            return this;
+        }
+
+        /**
+         * Sets the camera orbit pitch rotation.
+         */
+        @NonNull
+        public Builder setCameraOrbitPitchDegrees(
+                @FloatRange(from = -90.0f, to = 90.0f) float cameraOrbitPitchDegrees) {
+            mCameraOrbitPitchDegrees = cameraOrbitPitchDegrees;
+            return this;
+        }
+
+        /**
+         * Sets the camera dolly distance.
+         */
+        @NonNull
+        public Builder setDollyDistanceInWorldSpace(float dollyDistanceInWorldSpace) {
+            mDollyDistanceInWorldSpace = dollyDistanceInWorldSpace;
+            return this;
+        }
+
+        /**
+         * Sets the camera vertical fov degree.
+         */
+        @NonNull
+        public Builder setVerticalFovDegrees(
+                @FloatRange(from = 0.0f, to = 180.0f, fromInclusive = false)
+                        float verticalFovDegrees) {
+            mVerticalFovDegrees = verticalFovDegrees;
+            return this;
+        }
+
+        /**
+         * Sets the frustum in near plane.
+         */
+        @NonNull
+        public Builder setFrustumNearInWorldSpace(
+                @FloatRange(from = 0.0f) float frustumNearInWorldSpace) {
+            mFrustumNearInWorldSpace = frustumNearInWorldSpace;
+            return this;
+        }
+
+        /**
+         * Sets the frustum in far plane.
+         */
+        @NonNull
+        public Builder setFrustumFarInWorldSpace(
+                @FloatRange(from = 0.0f) float frustumFarInWorldSpace) {
+            mFrustumFarInWorldSpace = frustumFarInWorldSpace;
+            return this;
+        }
+
+        /**
+         * Builds a new {@link CameraAttributes} instance.
+         */
+        @NonNull
+        public CameraAttributes build() {
+            return new CameraAttributes(mAnchorPointInWorldSpace,
+                    mAnchorPointInOutputUvSpace,
+                    mCameraOrbitYawDegrees,
+                    mCameraOrbitPitchDegrees,
+                    mDollyDistanceInWorldSpace,
+                    mVerticalFovDegrees,
+                    mFrustumNearInWorldSpace,
+                    mFrustumFarInWorldSpace);
+        }
+    }
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.aidl
similarity index 79%
rename from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
rename to core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.aidl
index cb602d79..2347746 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021, The Android Open Source Project
+ * Copyright (c) 2022, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net;
+package android.app.wallpapereffectsgeneration;
 
-parcelable NetworkStateSnapshot;
+parcelable CinematicEffectRequest;
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java
new file mode 100644
index 0000000..f2e3313
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link CinematicEffectRequest} is the data class having all the information
+ * passed to wallpaper effects generation service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CinematicEffectRequest implements Parcelable {
+    /**
+     * Unique id of a cienmatic effect generation task.
+     */
+    @NonNull
+    private String mTaskId;
+
+    /**
+     * The bitmap to generate cinematic effect from.
+     */
+    @NonNull
+    private Bitmap mBitmap;
+
+    private CinematicEffectRequest(Parcel in) {
+        this.mTaskId = in.readString();
+        this.mBitmap = Bitmap.CREATOR.createFromParcel(in);
+    }
+
+    /**
+     * Constructor with task id and bitmap.
+     */
+    public CinematicEffectRequest(@NonNull String taskId, @NonNull Bitmap bitmap) {
+        mTaskId = taskId;
+        mBitmap = bitmap;
+    }
+
+    /**
+     * Returns the task id.
+     */
+    @NonNull
+    public String getTaskId() {
+        return mTaskId;
+    }
+
+    /**
+     * Returns the bitmap of this request.
+     */
+    @NonNull
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        CinematicEffectRequest that = (CinematicEffectRequest) o;
+        return mTaskId.equals(that.mTaskId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTaskId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mTaskId);
+        mBitmap.writeToParcel(out, flags);
+    }
+
+    @NonNull
+    public static final Creator<CinematicEffectRequest> CREATOR =
+            new Creator<CinematicEffectRequest>() {
+                @Override
+                public CinematicEffectRequest createFromParcel(Parcel in) {
+                    return new CinematicEffectRequest(in);
+                }
+
+                @Override
+                public CinematicEffectRequest[] newArray(int size) {
+                    return new CinematicEffectRequest[size];
+                }
+            };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.aidl
similarity index 79%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.aidl
index cb602d79..1bd1e1e 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021, The Android Open Source Project
+ * Copyright (c) 2022, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net;
+ package android.app.wallpapereffectsgeneration;
 
-parcelable NetworkStateSnapshot;
+ parcelable CinematicEffectResponse;
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
new file mode 100644
index 0000000..1254794
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wallpapereffectsgeneration;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link CinematicEffectResponse} include textured meshes
+ * and camera attributes of key frames.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CinematicEffectResponse implements Parcelable {
+    /** @hide */
+    @IntDef(prefix = {"CINEMATIC_EFFECT_STATUS_"},
+            value = {CINEMATIC_EFFECT_STATUS_UNKNOWN,
+                    CINEMATIC_EFFECT_STATUS_OK,
+                    CINEMATIC_EFFECT_STATUS_ERROR,
+                    CINEMATIC_EFFECT_STATUS_NOT_READY,
+                    CINEMATIC_EFFECT_STATUS_PENDING,
+                    CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CinematicEffectStatusCode {}
+
+    /** Cinematic effect generation unknown status. */
+    public static final int CINEMATIC_EFFECT_STATUS_UNKNOWN = 0;
+    /** Cinematic effect generation success. */
+    public static final int CINEMATIC_EFFECT_STATUS_OK = 1;
+    /** Cinematic effect generation failure. */
+    public static final int CINEMATIC_EFFECT_STATUS_ERROR = 2;
+    /** Service not ready for cinematic effect generation. */
+    public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 3;
+    /** Cienmatic effect generation process is pending. */
+    public static final int CINEMATIC_EFFECT_STATUS_PENDING = 4;
+    /** Too manay requests for server to handle. */
+    public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 5;
+
+    /** @hide */
+    @IntDef(prefix = {"IMAGE_CONTENT_TYPE_"},
+            value = {IMAGE_CONTENT_TYPE_UNKNOWN,
+                    IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT,
+                    IMAGE_CONTENT_TYPE_LANDSCAPE,
+                    IMAGE_CONTENT_TYPE_OTHER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ImageContentType {}
+
+    /** Image content unknown. */
+    public static final int IMAGE_CONTENT_TYPE_UNKNOWN = 0;
+    /** Image content is people portrait. */
+    public static final int IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT = 1;
+    /** Image content is landscape. */
+    public static final int IMAGE_CONTENT_TYPE_LANDSCAPE = 2;
+    /** Image content is doesn't belong to other types. */
+    public static final int IMAGE_CONTENT_TYPE_OTHER = 3;
+
+
+    @CinematicEffectStatusCode
+    private int mStatusCode;
+
+    /** The id of the cinematic effect generation task. */
+    @NonNull
+    private String mTaskId;
+
+    @ImageContentType
+    private int mImageContentType;
+
+    /** The textured mesh required to render cinematic effect. */
+    @NonNull
+    private List<TexturedMesh> mTexturedMeshes;
+
+    /** The start camera position for animation. */
+    @Nullable
+    private CameraAttributes mStartKeyFrame;
+
+    /** The end camera position for animation. */
+    @Nullable
+    private CameraAttributes mEndKeyFrame;
+
+    private CinematicEffectResponse(Parcel in) {
+        mStatusCode = in.readInt();
+        mTaskId = in.readString();
+        mImageContentType = in.readInt();
+        mTexturedMeshes = new ArrayList<TexturedMesh>();
+        in.readTypedList(mTexturedMeshes, TexturedMesh.CREATOR);
+        mStartKeyFrame = in.readTypedObject(CameraAttributes.CREATOR);
+        mEndKeyFrame = in.readTypedObject(CameraAttributes.CREATOR);
+    }
+
+    private CinematicEffectResponse(@CinematicEffectStatusCode int statusCode,
+            String taskId,
+            @ImageContentType int imageContentType,
+            List<TexturedMesh> texturedMeshes,
+            CameraAttributes startKeyFrame,
+            CameraAttributes endKeyFrame) {
+        mStatusCode = statusCode;
+        mTaskId = taskId;
+        mImageContentType = imageContentType;
+        mStartKeyFrame = startKeyFrame;
+        mEndKeyFrame = endKeyFrame;
+        mTexturedMeshes = texturedMeshes;
+    }
+
+    /** Gets the cinematic effect generation status code. */
+    @CinematicEffectStatusCode
+    public int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /** Get the task id. */
+    @NonNull
+    public String getTaskId() {
+        return mTaskId;
+    }
+
+    /**
+     * Get the image content type, which briefly classifies what's
+     * the content of image, like people portrait, landscape etc.
+     */
+    @ImageContentType
+    public int getImageContentType() {
+        return mImageContentType;
+    }
+
+    /** Get the textured meshes. */
+    @NonNull
+    public List<TexturedMesh> getTexturedMeshes() {
+        return mTexturedMeshes;
+    }
+
+    /**
+     * Get the camera attributes (position info and other parameters, see docs of
+     * {@link CameraAttributes}) of the start key frame on the animation path.
+     */
+    @Nullable
+    public CameraAttributes getStartKeyFrame() {
+        return mStartKeyFrame;
+    }
+
+    /**
+     * Get the camera attributes (position info and other parameters, see docs of
+     * {@link CameraAttributes}) of the end key frame on the animation path.
+     */
+    @Nullable
+    public CameraAttributes getEndKeyFrame() {
+        return mEndKeyFrame;
+    }
+
+    @NonNull
+    public static final Creator<CinematicEffectResponse> CREATOR =
+            new Creator<CinematicEffectResponse>() {
+                @Override
+                public CinematicEffectResponse createFromParcel(Parcel in) {
+                    return new CinematicEffectResponse(in);
+                }
+
+                @Override
+                public CinematicEffectResponse[] newArray(int size) {
+                    return new CinematicEffectResponse[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mStatusCode);
+        out.writeString(mTaskId);
+        out.writeInt(mImageContentType);
+        out.writeTypedList(mTexturedMeshes, flags);
+        out.writeTypedObject(mStartKeyFrame, flags);
+        out.writeTypedObject(mEndKeyFrame, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        CinematicEffectResponse that = (CinematicEffectResponse) o;
+        return mTaskId.equals(that.mTaskId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTaskId);
+    }
+    /**
+     * Builder of {@link CinematicEffectResponse}
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        @CinematicEffectStatusCode
+        private int mStatusCode;
+        @NonNull
+        private String mTaskId;
+        @ImageContentType
+        private int mImageContentType;
+        @NonNull
+        private List<TexturedMesh> mTexturedMeshes;
+        @Nullable
+        private CameraAttributes mStartKeyFrame;
+        @Nullable
+        private CameraAttributes mEndKeyFrame;
+
+        /**
+         * Constructor with task id and status code.
+         *
+         * @hide
+         */
+        @SystemApi
+        public Builder(@CinematicEffectStatusCode int statusCode, @NonNull String taskId) {
+            mStatusCode = statusCode;
+            mTaskId = taskId;
+        }
+
+        /**
+         * Sets the image content type.
+         */
+        @NonNull
+        public Builder setImageContentType(@ImageContentType int imageContentType) {
+            mImageContentType = imageContentType;
+            return this;
+        }
+
+
+        /**
+         * Sets the textured meshes.
+         */
+        @NonNull
+        public Builder setTexturedMeshes(@NonNull List<TexturedMesh> texturedMeshes) {
+            mTexturedMeshes = texturedMeshes;
+            return this;
+        }
+
+        /**
+         * Sets start key frame.
+         */
+        @NonNull
+        public Builder setStartKeyFrame(@Nullable CameraAttributes startKeyFrame) {
+            mStartKeyFrame = startKeyFrame;
+            return this;
+        }
+
+        /**
+         * Sets end key frame.
+         */
+        @NonNull
+        public Builder setEndKeyFrame(@Nullable CameraAttributes endKeyFrame) {
+            mEndKeyFrame = endKeyFrame;
+            return this;
+        }
+
+        /**
+         * Builds a {@link CinematicEffectResponse} instance.
+         */
+        @NonNull
+        public CinematicEffectResponse build() {
+            if (mTexturedMeshes == null) {
+                // Place holder because build doesn't allow list to be nullable.
+                mTexturedMeshes = new ArrayList<>(0);
+            }
+            return new CinematicEffectResponse(mStatusCode, mTaskId, mImageContentType,
+                    mTexturedMeshes, mStartKeyFrame, mEndKeyFrame);
+        }
+    }
+}
diff --git a/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.aidl b/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.aidl
new file mode 100644
index 0000000..c1a698d
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wallpapereffectsgeneration;
+
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+
+
+/**
+ * Callback used by system server to notify invoker of {@link WallpaperEffectsGenerationMAnager}
+ * of the cinematic effect generation result.
+ *
+ * @hide
+ */
+oneway interface ICinematicEffectListener {
+  void onCinematicEffectGenerated(in CinematicEffectResponse response);
+}
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl b/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl
new file mode 100644
index 0000000..706a89c
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl
@@ -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 android.app.wallpapereffectsgeneration;
+
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+
+/**
+ * Used by {@link android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager}
+ * to to generate effects.
+ *
+ * @hide
+ */
+oneway interface IWallpaperEffectsGenerationManager {
+  void generateCinematicEffect(in CinematicEffectRequest request,
+        in ICinematicEffectListener listener);
+
+  void returnCinematicEffectResponse(in CinematicEffectResponse response);
+}
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java b/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java
new file mode 100644
index 0000000..630de45
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wallpapereffectsgeneration;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The textured mesh representation, including a texture (bitmap) to sample from when rendering,
+ * and a mesh consisting of primitives such as triangles. The mesh is represented by an indices
+ * array describing the set of primitives in the mesh, and a vertices array that the indices
+ * refer to.
+ *
+ * @hide
+ */
+@SystemApi
+public final class TexturedMesh implements Parcelable {
+    /**
+     * The texture to sample from when rendering mesh.
+     */
+    @NonNull
+    private Bitmap mBitmap;
+
+    /**
+     * The set of primitives as pointers into the vertices.
+     */
+    @NonNull
+    private int[] mIndices;
+
+    /**
+     * The specific vertices that the indices refer to.
+     */
+    @NonNull
+    private float[] mVertices;
+
+    /** @hide */
+    @IntDef(prefix = {"INDICES_LAYOUT_"}, value = {
+            INDICES_LAYOUT_UNDEFINED,
+            INDICES_LAYOUT_TRIANGLES})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface IndicesLayoutType {
+    }
+
+    /** Undefined indices layout */
+    public static final int INDICES_LAYOUT_UNDEFINED = 0;
+    /**
+     * Indices layout is triangle. Vertices are grouped into 3 and each
+     * group forms a triangle.
+     */
+    public static final int INDICES_LAYOUT_TRIANGLES = 1;
+
+    @IndicesLayoutType
+    private int mIndicesLayoutType;
+
+    /** @hide */
+    @IntDef(prefix = {"VERTICES_LAYOUT_"}, value = {
+            VERTICES_LAYOUT_UNDEFINED,
+            VERTICES_LAYOUT_POSITION3_UV2})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VerticesLayoutType {
+    }
+
+    /**
+     * Undefined vertices layout.
+     */
+    public static final int VERTICES_LAYOUT_UNDEFINED = 0;
+    /**
+     * The vertices array uses 5 numbers to represent a point, in the format
+     * of [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+     */
+    public static final int VERTICES_LAYOUT_POSITION3_UV2 = 1;
+
+    @VerticesLayoutType
+    private int mVerticesLayoutType;
+
+    private TexturedMesh(Parcel in) {
+        this.mIndicesLayoutType = in.readInt();
+        this.mVerticesLayoutType = in.readInt();
+        this.mBitmap = in.readTypedObject(Bitmap.CREATOR);
+        Parcel data = Parcel.obtain();
+        try {
+            byte[] bytes = in.readBlob();
+            data.unmarshall(bytes, 0, bytes.length);
+            data.setDataPosition(0);
+            this.mIndices = data.createIntArray();
+            this.mVertices = data.createFloatArray();
+        } finally {
+            data.recycle();
+        }
+    }
+
+    private TexturedMesh(@NonNull Bitmap bitmap, @NonNull int[] indices,
+            @NonNull float[] vertices, @IndicesLayoutType int indicesLayoutType,
+            @VerticesLayoutType int verticesLayoutType) {
+        mBitmap = bitmap;
+        mIndices = indices;
+        mVertices = vertices;
+        mIndicesLayoutType = indicesLayoutType;
+        mVerticesLayoutType = verticesLayoutType;
+    }
+
+    /** Get the bitmap, which is the texture to sample from when rendering. */
+    @NonNull
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    /**
+     * Get the indices as pointers to the vertices array. Depending on the getIndicesLayoutType(),
+     * the primitives may have different shapes. For example, with INDICES_LAYOUT_TRIANGLES,
+     * indices 0, 1, 2 forms a triangle, indices 3, 4, 5 form another triangle.
+     */
+    @NonNull
+    public int[] getIndices() {
+        return mIndices;
+    }
+
+    /**
+     * Get the vertices that the index array refers to. Depending on the getVerticesLayoutType()
+     * result, the vertices array can represent different per-vertex coordinates. For example,
+     * with VERTICES_LAYOUT_POSITION3_UV2 type, vertices are in the format of
+     * [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+     */
+    @NonNull
+    public float[] getVertices() {
+        return mVertices;
+    }
+
+    /** Get the indices layout type. */
+    @IndicesLayoutType
+    @NonNull
+    public int getIndicesLayoutType() {
+        return mIndicesLayoutType;
+    }
+
+    /** Get the indices layout type. */
+    @VerticesLayoutType
+    @NonNull
+    public int getVerticesLayoutType() {
+        return mVerticesLayoutType;
+    }
+
+    @NonNull
+    public static final Creator<TexturedMesh> CREATOR = new Creator<TexturedMesh>() {
+        @Override
+        public TexturedMesh createFromParcel(Parcel in) {
+            return new TexturedMesh(in);
+        }
+
+        @Override
+        public TexturedMesh[] newArray(int size) {
+            return new TexturedMesh[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mIndicesLayoutType);
+        out.writeInt(mVerticesLayoutType);
+        out.writeTypedObject(mBitmap, flags);
+
+        // Indices and vertices can reach 5MB. Write the data as a Blob,
+        // which will be written to ashmem if too large.
+        Parcel data = Parcel.obtain();
+        try {
+            data.writeIntArray(mIndices);
+            data.writeFloatArray(mVertices);
+            out.writeBlob(data.marshall());
+        } finally {
+            data.recycle();
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * A builder for {@link TexturedMesh}
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        private Bitmap mBitmap;
+        private int[] mIndices;
+        private float[] mVertices;
+        @IndicesLayoutType
+        private int mIndicesLayoutType;
+        @VerticesLayoutType
+        private int mVerticesLayouttype;
+
+        /**
+         * Constructor with bitmap.
+         *
+         * @hide
+         */
+        @SystemApi
+        public Builder(@NonNull Bitmap bitmap) {
+            mBitmap = bitmap;
+        }
+
+        /**
+         * Set the required indices. The indices should represent the primitives. For example,
+         * with INDICES_LAYOUT_TRIANGLES, indices 0, 1, 2 forms a triangle, indices 3, 4, 5
+         * form another triangle.
+         */
+        @NonNull
+        public Builder setIndices(@NonNull int[] indices) {
+            mIndices = indices;
+            return this;
+        }
+
+        /**
+         * Set the required vertices. The vertices array should represent per-vertex coordinates.
+         * For example, with VERTICES_LAYOUT_POSITION3_UV2 type, vertices are in the format of
+         * [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+         *
+         */
+        @NonNull
+        public Builder setVertices(@NonNull float[] vertices) {
+            mVertices = vertices;
+            return this;
+        }
+
+        /**
+         * Set the required indices layout type.
+         */
+        @NonNull
+        public Builder setIndicesLayoutType(@IndicesLayoutType int indicesLayoutType) {
+            mIndicesLayoutType = indicesLayoutType;
+            return this;
+        }
+
+        /**
+         * Set the required vertices layout type.
+         */
+        @NonNull
+        public Builder setVerticesLayoutType(@VerticesLayoutType int verticesLayoutype) {
+            mVerticesLayouttype = verticesLayoutype;
+            return this;
+        }
+
+        /** Builds a TexturedMesh based on the given parameters. */
+        @NonNull
+        public TexturedMesh build() {
+            return new TexturedMesh(mBitmap, mIndices, mVertices, mIndicesLayoutType,
+                    mVerticesLayouttype);
+        }
+    }
+}
diff --git a/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java b/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java
new file mode 100644
index 0000000..5a1d27d
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wallpapereffectsgeneration;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link WallpaperEffectsGenerationManager} is the class that passes wallpaper effects
+ * generation requests to wallpaper effect generation service. For example, create a cinematic
+ * and render a cinematic live wallpaper with the response.
+ *
+ * Usage:
+ * <pre>{@code
+ *      mWallpaperEffectsGenerationManager =
+ *          context.getSystemService(WallpaperEffectsGenerationManager.class);
+ *      mWallpaperEffectsGenerationManager.
+ *          generateCinematicEffect(cinematicEffectRequest, response->{
+ *              // proceed cinematic effect response.
+ *          });
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE)
+public final class WallpaperEffectsGenerationManager {
+    /**
+     * Interface for the cinematic effect listener.
+     */
+    public interface CinematicEffectListener {
+        /**
+         * Async call when the cinematic effect response is generated.
+         * Client needs to check the status code of {@link CinematicEffectResponse}
+         * to determine if the effect generation is successful.
+         *
+         * @param response The generated cinematic effect response.
+         */
+        void onCinematicEffectGenerated(@NonNull CinematicEffectResponse response);
+    }
+
+    private final IWallpaperEffectsGenerationManager mService;
+
+    /** @hide */
+    public WallpaperEffectsGenerationManager(
+            @NonNull IWallpaperEffectsGenerationManager service) {
+        mService = service;
+    }
+
+    /**
+     * Execute a {@link android.app.wallpapereffectsgeneration.CinematicEffectRequest} from
+     * the given parameters to the wallpaper effects generation service. After the cinematic
+     * effect response is ready, the given listener is invoked by the system with the response.
+     * The listener may never receive a callback if unexpected error happened when proceeding
+     * request.
+     *
+     * @param request  request to generate cinematic effect.
+     * @param executor where the listener is invoked.
+     * @param listener listener invoked when the cinematic effect response is available.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION)
+    public void generateCinematicEffect(@NonNull CinematicEffectRequest request,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CinematicEffectListener listener) {
+        try {
+            mService.generateCinematicEffect(request,
+                    new CinematicEffectListenerWrapper(listener, executor));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static final class CinematicEffectListenerWrapper
+            extends ICinematicEffectListener.Stub {
+        @NonNull
+        private final CinematicEffectListener mListener;
+        @NonNull
+        private final Executor mExecutor;
+
+        CinematicEffectListenerWrapper(@NonNull CinematicEffectListener listener,
+                @NonNull Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCinematicEffectGenerated(CinematicEffectResponse response) {
+            mExecutor.execute(() -> mListener.onCinematicEffectGenerated(response));
+        }
+    }
+}
diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 941e9e2e..4e00da1 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -46,6 +46,25 @@
      */
     public abstract void cancelAttentionCheck(AttentionCallbackInternal callback);
 
+    /**
+     * Requests the continuous updates of proximity signal via the provided callback,
+     * until the given callback is unregistered. Currently, AttentionManagerService only
+     * anticipates one client and updates one client at a time. If a new client wants to
+     * onboard to receiving Proximity updates, please make a feature request to make proximity
+     * feature multi-client before depending on this feature.
+     *
+     * @param callback      a callback that receives the proximity updates
+     * @return {@code true} if the registration should succeed.
+     */
+    public abstract boolean onStartProximityUpdates(ProximityCallbackInternal callback);
+
+    /**
+     * Requests to stop providing continuous updates until the callback is registered.
+     *
+     * @param callback a callback that was used in {@link #onStartProximityUpdates}
+     */
+    public abstract void onStopProximityUpdates(ProximityCallbackInternal callback);
+
     /** Internal interface for attention callback. */
     public abstract static class AttentionCallbackInternal {
         /**
@@ -64,4 +83,13 @@
          */
         public abstract void onFailure(int error);
     }
+
+    /** Internal interface for proximity callback. */
+    public abstract static class ProximityCallbackInternal {
+        /**
+         * @param distance the estimated distance of the user (in meter)
+         * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
+         */
+        public abstract void onProximityUpdate(double distance);
+    }
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 2ddfeb4..1d0f7c0 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.os.Parcel;
@@ -106,11 +107,13 @@
     }
 
     /**
-     * Returns the set of activities allowed to be streamed, or {@code null} if this is not set.
+     * Returns the set of activities allowed to be streamed, or {@code null} if all activities are
+     * allowed, except the ones explicitly blocked.
      *
      * @see Builder#setAllowedActivities(Set)
-     * @hide  // TODO(b/194949534): Unhide this API
      */
+    // Null and empty have different semantics - Null allows all activities to be streamed
+    @SuppressLint("NullableCollection")
     @Nullable
     public Set<ComponentName> getAllowedActivities() {
         if (mAllowedActivities == null) {
@@ -120,12 +123,13 @@
     }
 
     /**
-     * Returns the set of activities that are blocked from streaming, or {@code null} if this is not
-     * set.
+     * Returns the set of activities that are blocked from streaming, or {@code null} to indicate
+     * that all activities in {@link #getAllowedActivities} are allowed.
      *
      * @see Builder#setBlockedActivities(Set)
-     * @hide  // TODO(b/194949534): Unhide this API
      */
+    // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+    @SuppressLint("NullableCollection")
     @Nullable
     public Set<ComponentName> getBlockedActivities() {
         if (mBlockedActivities == null) {
@@ -255,8 +259,10 @@
          *
          * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
          *   in the virtual device.
-         * @hide  // TODO(b/194949534): Unhide this API
          */
+        // Null and empty have different semantics - Null allows all activities to be streamed
+        @SuppressLint("NullableCollection")
+        @NonNull
         public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) {
             if (mBlockedActivities != null && allowedActivities != null) {
                 throw new IllegalArgumentException(
@@ -279,8 +285,10 @@
          *
          * @param blockedActivities A set of {@link ComponentName} to be blocked launching from
          *   virtual device.
-         * @hide  // TODO(b/194949534): Unhide this API
          */
+        // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+        @SuppressLint("NullableCollection")
+        @NonNull
         public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) {
             if (mAllowedActivities != null && blockedActivities != null) {
                 throw new IllegalArgumentException(
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4b4e008..52681630 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@
 import android.annotation.UiContext;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.BroadcastOptions;
 import android.app.GameManager;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
@@ -2260,6 +2261,27 @@
     }
 
     /**
+     * Version of {@link #sendBroadcastMultiplePermissions(Intent, String[])} that allows you to
+     * specify the {@link android.app.BroadcastOptions}.
+     *
+     * @param intent The Intent to broadcast; all receivers matching this
+     *               Intent will receive the broadcast.
+     * @param receiverPermissions Array of names of permissions that a receiver must hold
+     *                            in order to receive your broadcast.
+     *                            If empty, no permissions are required.
+     * @param options Additional sending options, generated from a
+     *                {@link android.app.BroadcastOptions}.
+     * @see #sendBroadcastMultiplePermissions(Intent, String[])
+     * @see android.app.BroadcastOptions
+     * @hide
+     */
+    @SystemApi
+    public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+            @NonNull String[] receiverPermissions, @Nullable BroadcastOptions options) {
+       sendBroadcastMultiplePermissions(intent, receiverPermissions, options.toBundle());
+    }
+
+    /**
      * Broadcast the given intent to all interested BroadcastReceivers, allowing
      * an array of required permissions to be enforced.  This call is asynchronous; it returns
      * immediately, and you will continue executing while the receivers are run.  No results are
@@ -4987,10 +5009,8 @@
      * @hide
      * @see #getSystemService(String)
      */
-    // TODO(216507592): Change cloudsearch_service to cloudsearch.
     @SystemApi
-    @SuppressLint("ServiceName")
-    public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service";
+    public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
 
     /**
      * Use with {@link #getSystemService(String)} to access the
@@ -5011,6 +5031,20 @@
     public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
 
     /**
+     * Used for getting the wallpaper effects generation service.
+     *
+     * <p><b>NOTE: </b> this service is optional; callers of
+     * {@code Context.getSystemServiceName(WALLPAPER_EFFECTS_GENERATION_SERVICE)} should check for
+     * {@code null}.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    @SystemApi
+    public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE =
+            "wallpaper_effects_generation";
+
+    /**
      * Used to access {@link MusicRecognitionManagerService}.
      *
      * @hide
@@ -5638,6 +5672,15 @@
     public static final String OVERLAY_SERVICE = "overlay";
 
     /**
+     * Use with {@link #getSystemService(String)} to manage resources.
+     *
+     * @see #getSystemService(String)
+     * @see com.android.server.resources.ResourcesManagerService
+     * @hide
+     */
+    public static final String RESOURCES_SERVICE = "resources";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a
      * {android.os.IIdmap2} for managing idmap files (used by overlay
      * packages).
@@ -6677,21 +6720,27 @@
             @NonNull Configuration overrideConfiguration);
 
     /**
-     * Returns a new <code>Context</code> object from the current context but with resources
-     * adjusted to match the metrics of <code>display</code>. Each call to this method
+     * Returns a new {@code Context} object from the current context but with resources
+     * adjusted to match the metrics of {@code display}. Each call to this method
      * returns a new instance of a context object. Context objects are not shared; however,
      * common state (such as the {@link ClassLoader} and other resources for the same
-     * configuration) can be shared, so the <code>Context</code> itself is lightweight.
+     * configuration) can be shared, so the {@code Context} itself is lightweight.
+     *
+     * <p><b>Note:</b>
+     * This {@code Context} is <b>not</b> expected to be updated with new configuration if the
+     * underlying display configuration changes and the cached {@code Resources} it returns
+     * could be stale. It is suggested to use
+     * {@link android.hardware.display.DisplayManager.DisplayListener} to listen for
+     * changes and re-create an instance if necessary. </p>
      * <p>
+     * This {@code Context} is <b>not</b> a UI context, do not use it to access UI components
+     * or obtain a {@link WindowManager} instance.
+     * </p><p>
      * To obtain an instance of {@link WindowManager} configured to show windows on the given
      * display, call {@link #createWindowContext(int, Bundle)} on the returned display context,
      * then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the
      * returned window context.
-     * <p>
-     * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI
-     * context. Do not access UI components or obtain a {@link WindowManager} from the context
-     * created by <code>createDisplayContext(Display)</code>.
-     *
+     * </p>
      * @param display The display to which the current context's resources are adjusted.
      *
      * @return A context for the display.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index fb186fd..3e527f8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3986,7 +3986,7 @@
      * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SystemApi
     public static final String ACTION_USER_SWITCHED =
             "android.intent.action.USER_SWITCHED";
 
@@ -6196,6 +6196,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @SuppressLint("ActionValue")
     public static final String EXTRA_USER_HANDLE =
             "android.intent.extra.user_handle";
 
@@ -7059,6 +7061,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
     /**
      * If set, the broadcast will never go to manifest receivers in background (cached
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 1e88758..94f0561 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -282,7 +282,8 @@
         final boolean isManagedProfile =
                 mUserManager.isManagedProfile(userHandle.getIdentifier());
         if (isManagedProfile) {
-            return mResources.getDrawable(R.drawable.ic_corp_badge, null);
+            return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
+                    userHandle, /* density= */ 0);
         } else {
             return UserIcons.getDefaultUserIcon(
                     mResources, UserHandle.USER_SYSTEM, true /* light */);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 1c82b38..30aed8b 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -653,6 +653,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     String getPermissionControllerPackageName();
+    String getSupplementalProcessPackageName();
 
     ParceledListSlice getInstantApps(int userId);
     byte[] getInstantAppCookie(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 9735f81..410e106 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -21,7 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 
 /**
  * Basic information about a package as specified in its manifest.
@@ -80,10 +80,10 @@
 
     /**
      * Specifies the recommended install location. Can be one of
-     * {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
-     * {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
-     * {@link PackageHelper#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
-     * or {@link PackageHelper#RECOMMEND_FAILED_INVALID_APK} for parse errors.
+     * {@link InstallLocationUtils#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
+     * {@link InstallLocationUtils#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
+     * {@link InstallLocationUtils#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
+     * or {@link InstallLocationUtils#RECOMMEND_FAILED_INVALID_APK} for parse errors.
      */
     public int recommendedInstallLocation;
     public int installLocation;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e5c31d7..aa64700 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5691,6 +5691,20 @@
     }
 
     /**
+     * Returns the package name of the component implementing supplemental process service.
+     *
+     * @return the package name of the component implementing supplemental process service
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public String getSupplementalProcessPackageName() {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Add a new dynamic permission to the system.  For this to work, your
      * package must have defined a permission tree through the
      * {@link android.R.styleable#AndroidManifestPermissionTree
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index 43a4b17..4c0e2e6 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -46,7 +46,7 @@
             TYPE_BUILTIN,
             TYPE_DYNAMIC,
             TYPE_STATIC,
-            TYPE_SDK,
+            TYPE_SDK_PACKAGE,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Type{}
@@ -68,15 +68,21 @@
      * Shared library type: this library is <strong>not</strong> backwards
      * -compatible, can be updated and updates can be uninstalled. Clients
      * link against a specific version of the library.
+     *
+     * Static shared libraries simulate static linking while allowing for
+     * multiple clients to reuse the same instance of the library.
      */
     public static final int TYPE_STATIC = 2;
 
     /**
-     * SDK library type: this library is <strong>not</strong> backwards
-     * -compatible, can be updated and updates can be uninstalled. Clients
-     * depend on a specific version of the library.
+     * SDK package shared library type: this library is <strong>not</strong>
+     * compatible between versions, can be updated and updates can be
+     * uninstalled. Clients depend on a specific version of the library.
+     *
+     * SDK packages are not loaded automatically by the OS and rely
+     * e.g. on 3P libraries to make them available for the clients.
      */
-    public static final int TYPE_SDK = 3;
+    public static final int TYPE_SDK_PACKAGE = 3;
 
     /**
      * Constant for referring to an undefined version.
@@ -301,7 +307,7 @@
      * @hide
      */
     public boolean isSdk() {
-        return mType == TYPE_SDK;
+        return mType == TYPE_SDK_PACKAGE;
     }
 
     /**
@@ -367,7 +373,7 @@
             case TYPE_STATIC: {
                 return "static";
             }
-            case TYPE_SDK: {
+            case TYPE_SDK_PACKAGE: {
                 return "sdk";
             }
             default: {
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index a503d14..dea0834 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -162,5 +162,68 @@
     {
       "name": "CtsInstallHostTestCases"
     }
+  ],
+  "staged-platinum-postsubmit": [
+    {
+      "name": "CtsIncrementalInstallHostTestCases"
+    },
+    {
+      "name": "CtsInstallHostTestCases"
+    },
+    {
+      "name": "CtsStagedInstallHostTestCases"
+    },
+    {
+      "name": "CtsExtractNativeLibsHostTestCases"
+    },
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "com.android.cts.splitapp.SplitAppTest"
+        },
+        {
+          "include-filter": "android.appsecurity.cts.EphemeralTest"
+        }
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.PackageParserTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsRollbackManagerHostTestCases"
+    },
+    {
+      "name": "CtsOsHostTestCases",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.PackageParserTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "include-filter": "android.content.cts.IntentFilterTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsAppEnumerationTestCases"
+    },
+    {
+      "name": "PackageManagerServiceUnitTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.test.verify.domain"
+        }
+      ]
+    }
   ]
 }
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 6fd2d05..7a5ac8e 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -28,6 +28,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
@@ -438,6 +439,12 @@
         }
     }
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "class=" + getClass());
+        pw.println(prefix + "debugName=" + getDebugName());
+        pw.println(prefix + "assetPath=" + getAssetPath());
+    }
+
     private static native long nativeLoad(@FormatType int format, @NonNull String path,
             @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
     private static native long nativeLoadEmpty(@PropertyFlags int flags,
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index bfd9fd0..a05f5c9 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -43,6 +43,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.lang.ref.Reference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1531,6 +1532,15 @@
         }
     }
 
+    synchronized void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "class=" + getClass());
+        pw.println(prefix + "apkAssets=");
+        for (int i = 0; i < mApkAssets.length; i++) {
+            pw.println(prefix + i);
+            mApkAssets[i].dump(pw, prefix + "  ");
+        }
+    }
+
     // AssetManager setup native methods.
     private static native long nativeCreate();
     private static native void nativeDestroy(long ptr);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/content/res/IResourcesManager.aidl
similarity index 67%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/content/res/IResourcesManager.aidl
index 861a4ed..d137378 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/content/res/IResourcesManager.aidl
@@ -14,6 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.mediattt;
+package android.content.res;
 
-parcelable DeviceInfo;
+import android.os.RemoteCallback;
+
+/**
+ * Api for getting information about resources.
+ *
+ * {@hide}
+ */
+interface IResourcesManager {
+    boolean dumpResources(in String process,
+        in ParcelFileDescriptor fd,
+        in RemoteCallback finishCallback);
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 5fd0d84..ebef053 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -53,6 +53,7 @@
 import android.graphics.drawable.DrawableInflater;
 import android.os.Build;
 import android.os.Bundle;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -78,11 +79,15 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
 
 /**
  * Class for accessing an application's resources.  This sits on top of the
@@ -172,6 +177,11 @@
 
     private int mBaseApkAssetsSize;
 
+    /** @hide */
+    private static Set<Resources> sResourcesHistory = Collections.synchronizedSet(
+            Collections.newSetFromMap(
+                    new WeakHashMap<>()));
+
     /**
      * Returns the most appropriate default theme for the specified target SDK version.
      * <ul>
@@ -318,6 +328,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public Resources(@Nullable ClassLoader classLoader) {
         mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
+        sResourcesHistory.add(this);
     }
 
     /**
@@ -2649,4 +2660,29 @@
             }
         }
     }
+
+    /** @hide */
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "class=" + getClass());
+        pw.println(prefix + "resourcesImpl");
+        mResourcesImpl.dump(pw, prefix + "  ");
+    }
+
+    /** @hide */
+    public static void dumpHistory(PrintWriter pw, String prefix) {
+        pw.println(prefix + "history");
+        // Putting into a map keyed on the apk assets to deduplicate resources that are different
+        // objects but ultimately represent the same assets
+        Map<List<ApkAssets>, Resources> history = new ArrayMap<>();
+        for (Resources r : sResourcesHistory) {
+            history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r);
+        }
+        int i = 0;
+        for (Resources r : history.values()) {
+            if (r != null) {
+                pw.println(prefix + i++);
+                r.dump(pw, prefix + "  ");
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 4d850b0c..ff07291 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -61,6 +61,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -1271,6 +1272,12 @@
             NativeAllocationRegistry.createMalloced(ResourcesImpl.class.getClassLoader(),
                     AssetManager.getThemeFreeFunction());
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "class=" + getClass());
+        pw.println(prefix + "assets");
+        mAssets.dump(pw, prefix + "  ");
+    }
+
     public class ThemeImpl {
         /**
          * Unique key for the series of styles applied to this theme.
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index f13c795..52bba14 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -131,11 +131,16 @@
      *
      * @param name The name of the cursor window, or null if none.
      * @param windowSizeBytes Size of cursor window in bytes.
+     * @throws IllegalArgumentException if {@code windowSizeBytes} is less than 0
+     * @throws AssertionError if created window pointer is 0
      * <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
      * window. Depending on the amount of data stored, the actual amount of memory allocated can be
      * lower than specified size, but cannot exceed it.
      */
     public CursorWindow(String name, @BytesLong long windowSizeBytes) {
+        if (windowSizeBytes < 0) {
+            throw new IllegalArgumentException("Window size cannot be less than 0");
+        }
         mStartPos = 0;
         mName = name != null && name.length() != 0 ? name : "<unnamed>";
         mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java
index 2e3227d..5315c8b 100644
--- a/core/java/android/database/CursorWindowAllocationException.java
+++ b/core/java/android/database/CursorWindowAllocationException.java
@@ -16,14 +16,14 @@
 
 package android.database;
 
+import android.annotation.NonNull;
+
 /**
  * This exception is thrown when a CursorWindow couldn't be allocated,
  * most probably due to memory not being available.
- *
- * @hide
  */
 public class CursorWindowAllocationException extends RuntimeException {
-    public CursorWindowAllocationException(String description) {
+    public CursorWindowAllocationException(@NonNull String description) {
         super(description);
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
similarity index 61%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/hardware/biometrics/IBiometricContextListener.aidl
index 861a4ed..55cab52 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -13,7 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.hardware.biometrics;
 
-package com.android.systemui.shared.mediattt;
-
-parcelable DeviceInfo;
+/**
+ * A secondary communication channel from AuthController back to BiometricService for
+ * events that are not associated with an autentication session. See
+ * {@link IBiometricSysuiReceiver} for events associated with a session.
+ *
+ * @hide
+ */
+oneway interface IBiometricContextListener {
+    void onDozeChanged(boolean isDozing);
+}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c12e819..d6d3a97 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -25,6 +25,7 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.Point;
 import android.hardware.CameraStatus;
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
@@ -458,12 +459,14 @@
                     (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
             Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
             if (display != null) {
-                int width = display.getWidth();
-                int height = display.getHeight();
+                Point sz = new Point();
+                display.getRealSize(sz);
+                int width = sz.x;
+                int height = sz.y;
 
                 if (height > width) {
                     height = width;
-                    width = display.getHeight();
+                    width = sz.y;
                 }
 
                 ret = new Size(width, height);
@@ -471,7 +474,7 @@
                 Log.e(TAG, "Invalid default display!");
             }
         } catch (Exception e) {
-            Log.e(TAG, "getDisplaySize Failed. " + e.toString());
+            Log.e(TAG, "getDisplaySize Failed. " + e);
         }
 
         return ret;
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index b06d076..30aa4db 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -79,9 +79,9 @@
      * <ul>
      *     <li>The system deems the request can no longer be honored, for example if the requested
      *     state becomes unsupported.
-     *     <li>A call to {@link #cancelRequest(DeviceStateRequest)}.
+     *     <li>A call to {@link #cancelStateRequest}.
      *     <li>Another processes submits a request succeeding this request in which case the request
-     *     will be suspended until the interrupting request is canceled.
+     *     will be canceled.
      * </ul>
      * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
      *
@@ -100,19 +100,18 @@
     }
 
     /**
-     * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
+     * Cancels the active {@link DeviceStateRequest} previously submitted with a call to
      * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
      * <p>
-     * This method is noop if the {@code request} has not been submitted with a call to
-     * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+     * This method is noop if there is no request currently active.
      *
      * @throws SecurityException if the caller is neither the current top-focused activity nor if
      * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
      */
     @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
             conditional = true)
-    public void cancelRequest(@NonNull DeviceStateRequest request) {
-        mGlobal.cancelRequest(request);
+    public void cancelStateRequest() {
+        mGlobal.cancelStateRequest();
     }
 
     /**
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 85e70b0..aba538f 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -151,20 +151,14 @@
      * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
      * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
      *
-     * @see DeviceStateManager#cancelRequest(DeviceStateRequest)
+     * @see DeviceStateManager#cancelStateRequest
      */
-    public void cancelRequest(@NonNull DeviceStateRequest request) {
+    public void cancelStateRequest() {
         synchronized (mLock) {
             registerCallbackIfNeededLocked();
 
-            final IBinder token = findRequestTokenLocked(request);
-            if (token == null) {
-                // This request has not been submitted.
-                return;
-            }
-
             try {
-                mDeviceStateManager.cancelRequest(token);
+                mDeviceStateManager.cancelStateRequest();
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -299,20 +293,6 @@
 
     /**
      * Handles a call from the server that a request for the supplied {@code token} has become
-     * suspended.
-     */
-    private void handleRequestSuspended(IBinder token) {
-        DeviceStateRequestWrapper request;
-        synchronized (mLock) {
-            request = mRequests.get(token);
-        }
-        if (request != null) {
-            request.notifyRequestSuspended();
-        }
-    }
-
-    /**
-     * Handles a call from the server that a request for the supplied {@code token} has become
      * canceled.
      */
     private void handleRequestCanceled(IBinder token) {
@@ -337,11 +317,6 @@
         }
 
         @Override
-        public void onRequestSuspended(IBinder token) {
-            handleRequestSuspended(token);
-        }
-
-        @Override
         public void onRequestCanceled(IBinder token) {
             handleRequestCanceled(token);
         }
@@ -395,14 +370,6 @@
             mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
         }
 
-        void notifyRequestSuspended() {
-            if (mCallback == null) {
-                return;
-            }
-
-            mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
-        }
-
         void notifyRequestCanceled() {
             if (mCallback == null) {
                 return;
diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java
index df488d2..893d765 100644
--- a/core/java/android/hardware/devicestate/DeviceStateRequest.java
+++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java
@@ -32,8 +32,7 @@
  * DeviceStateRequest.Callback)}.
  * <p>
  * By default, the request is kept active until a call to
- * {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following
- * occurs:
+ * {@link DeviceStateManager#cancelStateRequest} or until one of the following occurs:
  * <ul>
  *     <li>Another processes submits a request succeeding this request in which case the request
  *     will be suspended until the interrupting request is canceled.
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 14ed03d..e450e42 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -41,8 +41,9 @@
      * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
      * call to this method.
      *
-     * @param token the request token previously registered with
-     *        {@link #requestState(IBinder, int, int)}
+     * @param token the request token provided
+     * @param state the state of device the request is asking for
+     * @param flags any flags that correspond to the request
      *
      * @throws IllegalStateException if a callback has not yet been registered for the calling
      *         process.
@@ -52,14 +53,11 @@
     void requestState(IBinder token, int state, int flags);
 
     /**
-     * Cancels a request previously submitted with a call to
+     * Cancels the active request previously submitted with a call to
      * {@link #requestState(IBinder, int, int)}.
      *
-     * @param token the request token previously registered with
-     *        {@link #requestState(IBinder, int, int)}
-     *
-     * @throws IllegalStateException if the supplied {@code token} has not been previously
-     *         requested.
+     * @throws IllegalStateException if a callback has not yet been registered for the calling
+     *         process.
      */
-    void cancelRequest(IBinder token);
+    void cancelStateRequest();
 }
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
index efb9888..348690f 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
@@ -41,16 +41,6 @@
     oneway void onRequestActive(IBinder token);
 
     /**
-     * Called to notify the callback that a request has become suspended. Guaranteed to be called
-     * before a subsequent call to {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request
-     * becoming suspended resulted in a change of device state info.
-     *
-     * @param token the request token previously registered with
-     *        {@link IDeviceStateManager#requestState(IBinder, int, int)}
-     */
-    oneway void onRequestSuspended(IBinder token);
-
-    /**
      * Called to notify the callback that a request has become canceled. No further callbacks will
      * be triggered for this request. Guaranteed to be called before a subsequent call to
      * {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request becoming canceled resulted
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 1a7a63ae..af8ec27 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -916,6 +916,17 @@
     }
 
     /**
+     * Returns the system preferred display mode.
+     */
+    public Display.Mode getSystemPreferredDisplayMode(int displayId) {
+        try {
+            return mDm.getSystemPreferredDisplayMode(displayId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * When enabled the app requested display resolution and refresh rate is always selected
      * in DisplayModeDirector regardless of user settings and policies for low brightness, low
      * battery etc.
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 35663af..b3af52b 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -168,6 +168,7 @@
     // Requires MODIFY_USER_PREFERRED_DISPLAY_MODE permission.
     void setUserPreferredDisplayMode(int displayId, in Mode mode);
     Mode getUserPreferredDisplayMode(int displayId);
+    Mode getSystemPreferredDisplayMode(int displayId);
 
     // When enabled the app requested display resolution and refresh rate is always selected
     // in DisplayModeDirector regardless of user settings and policies for low brightness, low
diff --git a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
index e7d76f6..bb34646 100644
--- a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
+++ b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
@@ -142,7 +142,7 @@
      * @hide
      */
     public void setSystemAudioMode(boolean state, @NonNull SetSystemAudioModeCallback callback) {
-        // TODO(amyjojo): implement this when needed.
+        // TODO(b/217509829): implement this when needed.
     }
 
     /**
diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index cee6a5b..b96e4f8 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -21,6 +21,8 @@
 public abstract class HdmiClient {
     private static final String TAG = "HdmiClient";
 
+    private static final int UNKNOWN_VENDOR_ID = 0xFFFFFF;
+
     /* package */ final IHdmiControlService mService;
 
     private IHdmiVendorCommandListener mIHdmiVendorCommandListener;
@@ -156,11 +158,25 @@
     }
 
     /**
-     * Sets a listener used to receive incoming vendor-specific command.
+     * Sets a listener used to receive incoming vendor-specific command. This listener will only
+     * receive {@code <Vendor Command>} but will not receive any {@code <Vendor Command with ID>}
+     * messages.
      *
      * @param listener listener object
      */
     public void setVendorCommandListener(@NonNull VendorCommandListener listener) {
+        // Set the vendor ID to INVALID_VENDOR_ID.
+        setVendorCommandListener(listener, UNKNOWN_VENDOR_ID);
+    }
+
+    /**
+     * Sets a listener used to receive incoming vendor-specific command.
+     *
+     * @param listener listener object
+     * @param vendorId The listener is interested in {@code <Vendor Command with ID>} received with
+     *     this vendorId and all {@code <Vendor Command>} messages.
+     */
+    public void setVendorCommandListener(@NonNull VendorCommandListener listener, int vendorId) {
         if (listener == null) {
             throw new IllegalArgumentException("listener cannot be null");
         }
@@ -169,7 +185,7 @@
         }
         try {
             IHdmiVendorCommandListener wrappedListener = getListenerWrapper(listener);
-            mService.addVendorCommandListener(wrappedListener, getDeviceType());
+            mService.addVendorCommandListener(wrappedListener, vendorId);
             mIHdmiVendorCommandListener = wrappedListener;
         } catch (RemoteException e) {
             Log.e(TAG, "failed to set vendor command listener: ", e);
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 5874385..9235ba1 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -105,6 +105,12 @@
             "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
 
     /**
+     * Used as an extra field in the Set Menu Language intent. Contains the requested locale.
+     * @hide
+     */
+    public static final String EXTRA_LOCALE = "android.hardware.hdmi.extra.LOCALE";
+
+    /**
      * Volume value for mute state.
      */
     public static final int AVR_VOLUME_MUTED = 101;
@@ -558,6 +564,32 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface TvSendStandbyOnSleep {}
 
+    // -- Whether a playback device should act on an incoming {@code <Set Menu Language>} message.
+    /**
+     * Confirmation dialog should be shown upon receiving the CEC message.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+     * @hide
+     */
+    public static final int SET_MENU_LANGUAGE_ENABLED = 1;
+    /**
+     * The message should be ignored.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+     * @hide
+     */
+    public static final int SET_MENU_LANGUAGE_DISABLED = 0;
+    /**
+     * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+     * @hide
+     */
+    @IntDef(prefix = { "SET_MENU_LANGUAGE_" }, value = {
+            SET_MENU_LANGUAGE_ENABLED,
+            SET_MENU_LANGUAGE_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SetMenuLanguage {}
+
     // -- The RC profile of a TV panel.
     /**
      * RC profile none.
@@ -812,6 +844,13 @@
     public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP =
             "tv_send_standby_on_sleep";
     /**
+     * Name of a setting deciding whether {@code <Set Menu Language>} message should be
+     * handled by the framework or ignored.
+     *
+     * @hide
+     */
+    public static final String CEC_SETTING_NAME_SET_MENU_LANGUAGE = "set_menu_language";
+    /**
      * Name of a setting representing the RC profile of a TV panel.
      *
      * @hide
@@ -977,6 +1016,7 @@
         CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
         CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
         CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+        CEC_SETTING_NAME_SET_MENU_LANGUAGE,
         CEC_SETTING_NAME_RC_PROFILE_TV,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index 16adee9..818554d 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -221,8 +221,8 @@
         }
 
         @Override
-        public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
-            HdmiControlServiceWrapper.this.addVendorCommandListener(listener, deviceType);
+        public void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {
+            HdmiControlServiceWrapper.this.addVendorCommandListener(listener, vendorId);
         }
 
         @Override
@@ -481,7 +481,7 @@
             boolean hasVendorId) {}
 
     /** @hide */
-    public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {}
+    public void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {}
 
     /** @hide */
     public void sendStandby(int deviceType, int deviceId) {}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 6613397..35dd9ed 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -76,7 +76,7 @@
     void askRemoteDeviceToBecomeActiveSource(int physicalAddress);
     void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
             boolean hasVendorId);
-    void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
+    void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId);
     void sendStandby(int deviceType, int deviceId);
     void setHdmiRecordListener(IHdmiRecordListener callback);
     void startOneTouchRecord(int recorderAddress, in byte[] recordSource);
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 0304815..e1ffd4a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -112,7 +112,7 @@
     oneway void requestPointerCapture(IBinder inputChannelToken, boolean enabled);
 
     /** Create an input monitor for gestures. */
-    InputMonitor monitorGestureInput(String name, int displayId);
+    InputMonitor monitorGestureInput(IBinder token, String name, int displayId);
 
     // Add a runtime association between the input port and the display port. This overrides any
     // static associations.
@@ -122,9 +122,9 @@
     void removePortAssociation(in String inputPort);
 
     // Add a runtime association between the input device and display.
-    void addUniqueIdAssociation(in String inputDeviceName, in String displayUniqueId);
+    void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
     // Remove the runtime association between the input device and display.
-    void removeUniqueIdAssociation(in String inputDeviceName);
+    void removeUniqueIdAssociation(in String inputPort);
 
     InputSensorInfo[] getSensorList(int deviceId);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index cbc8373..2fd79cf 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -35,6 +35,7 @@
 import android.hardware.lights.LightState;
 import android.hardware.lights.LightsManager;
 import android.hardware.lights.LightsRequest;
+import android.os.Binder;
 import android.os.BlockUntrustedTouchesMode;
 import android.os.Build;
 import android.os.CombinedVibration;
@@ -1211,7 +1212,7 @@
      */
     public InputMonitor monitorGestureInput(String name, int displayId) {
         try {
-            return mIm.monitorGestureInput(name, displayId);
+            return mIm.monitorGestureInput(new Binder(), name, displayId);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -1359,19 +1360,18 @@
     }
 
     /**
-     * Add a runtime association between the input device name and display, by unique id. Input
-     * device names are expected to be unique.
-     * @param inputDeviceName The name of the input device.
+     * Add a runtime association between the input port and display, by unique id. Input ports are
+     * expected to be unique.
+     * @param inputPort The port of the input device.
      * @param displayUniqueId The unique id of the associated display.
      * <p>
      * Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
-    public void addUniqueIdAssociation(@NonNull String inputDeviceName,
-            @NonNull String displayUniqueId) {
+    public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
         try {
-            mIm.addUniqueIdAssociation(inputDeviceName, displayUniqueId);
+            mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1379,15 +1379,15 @@
 
     /**
      * Removes a runtime association between the input device and display.
-     * @param inputDeviceName The name of the input device.
+     * @param inputPort The port of the input device.
      * <p>
      * Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
-    public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+    public void removeUniqueIdAssociation(@NonNull String inputPort) {
         try {
-            mIm.removeUniqueIdAssociation(inputDeviceName);
+            mIm.removeUniqueIdAssociation(inputPort);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 60f5135..7ff74c6 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1341,7 +1341,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     boolean resetUsbPort(@NonNull UsbPort port, int operationId,
             IUsbOperationInternal callback) {
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 41642e7..af57f79 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -70,6 +70,7 @@
     private static final int DO_SET_INPUT_CONTEXT = 20;
     private static final int DO_UNSET_INPUT_CONTEXT = 30;
     private static final int DO_START_INPUT = 32;
+    private static final int DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED = 35;
     private static final int DO_CREATE_SESSION = 40;
     private static final int DO_SET_SESSION_ENABLED = 45;
     private static final int DO_SHOW_SOFT_INPUT = 60;
@@ -175,7 +176,7 @@
                 try {
                     inputMethod.initializeInternal((IBinder) args.arg1,
                             (IInputMethodPrivilegedOperations) args.arg2, msg.arg1,
-                            (boolean) args.arg3);
+                            (boolean) args.arg3, msg.arg2 != 0);
                 } finally {
                     args.recycle();
                 }
@@ -195,14 +196,22 @@
                 final EditorInfo info = (EditorInfo) args.arg3;
                 final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
                 final boolean restarting = args.argi5 == 1;
+                final boolean shouldShowImeSwitcherWhenImeIsShown = args.argi6 != 0;
                 final InputConnection ic = inputContext != null
                         ? new RemoteInputConnection(mTarget, inputContext, cancellationGroup)
                         : null;
                 info.makeCompatible(mTargetSdkVersion);
-                inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken);
+                inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
+                        shouldShowImeSwitcherWhenImeIsShown);
                 args.recycle();
                 return;
             }
+            case DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED: {
+                final boolean shouldShowImeSwitcherWhenImeIsShown = msg.arg1 != 0;
+                inputMethod.onShouldShowImeSwitcherWhenImeIsShownChanged(
+                        shouldShowImeSwitcherWhenImeIsShown);
+                return;
+            }
             case DO_CREATE_SESSION: {
                 SomeArgs args = (SomeArgs)msg.obj;
                 inputMethod.createSession(new InputMethodSessionCallbackWrapper(
@@ -291,10 +300,11 @@
     @BinderThread
     @Override
     public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-            int configChanges, boolean stylusHwSupported) {
-        mCaller.executeOrSendMessage(
-                mCaller.obtainMessageIOOO(
-                        DO_INITIALIZE_INTERNAL, configChanges, token, privOps, stylusHwSupported));
+            int configChanges, boolean stylusHwSupported,
+            boolean shouldShowImeSwitcherWhenImeIsShown) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL,
+                configChanges, shouldShowImeSwitcherWhenImeIsShown ? 1 : 0, token, privOps,
+                stylusHwSupported));
     }
 
     @BinderThread
@@ -334,13 +344,23 @@
     @BinderThread
     @Override
     public void startInput(IBinder startInputToken, IInputContext inputContext,
-            EditorInfo attribute, boolean restarting) {
+            EditorInfo attribute, boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) {
         if (mCancellationGroup == null) {
             Log.e(TAG, "startInput must be called after bindInput.");
             mCancellationGroup = new CancellationGroup();
         }
         mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
-                inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, 0 /* unused */));
+                inputContext, attribute, mCancellationGroup, restarting ? 1 : 0,
+                shouldShowImeSwitcherWhenImeIsShown ? 1 : 0));
+    }
+
+    @BinderThread
+    @Override
+    public void onShouldShowImeSwitcherWhenImeIsShownChanged(
+            boolean shouldShowImeSwitcherWhenImeIsShown) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageI(
+                DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED,
+                shouldShowImeSwitcherWhenImeIsShown ? 1 : 0));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 14f92fb..fc2fbc3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -346,7 +346,7 @@
      */
     @AnyThread
     public static boolean canImeRenderGesturalNavButtons() {
-        return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, false);
+        return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, true);
     }
 
     /**
@@ -475,7 +475,7 @@
     private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations();
 
     @NonNull
-    final NavigationBarController mNavigationBarController =
+    private final NavigationBarController mNavigationBarController =
             new NavigationBarController(this);
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -658,7 +658,7 @@
         @Override
         public final void initializeInternal(@NonNull IBinder token,
                 IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
-                boolean stylusHwSupported) {
+                boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) {
             if (mDestroyed) {
                 Log.i(TAG, "The InputMethodService has already onDestroyed()."
                     + "Ignore the initialization.");
@@ -671,6 +671,8 @@
             if (stylusHwSupported) {
                 mInkWindow = new InkWindow(mWindow.getContext());
             }
+            mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+                    shouldShowImeSwitcherWhenImeIsShown);
             attachToken(token);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -780,9 +782,10 @@
         @Override
         public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                 @NonNull EditorInfo editorInfo, boolean restarting,
-                @NonNull IBinder startInputToken) {
+                @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) {
             mPrivOps.reportStartInputAsync(startInputToken);
-
+            mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+                    shouldShowImeSwitcherWhenImeIsShown);
             if (restarting) {
                 restartInput(inputConnection, editorInfo);
             } else {
@@ -796,6 +799,18 @@
          */
         @MainThread
         @Override
+        public void onShouldShowImeSwitcherWhenImeIsShownChanged(
+                boolean shouldShowImeSwitcherWhenImeIsShown) {
+            mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+                    shouldShowImeSwitcherWhenImeIsShown);
+        }
+
+        /**
+         * {@inheritDoc}
+         * @hide
+         */
+        @MainThread
+        @Override
         public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
                 IBinder hideInputToken) {
             mSystemCallingHideSoftInput = true;
@@ -1489,7 +1504,7 @@
                 Context.LAYOUT_INFLATER_SERVICE);
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
         mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
-
+        mNavigationBarController.onSoftInputWindowCreated(mWindow);
         {
             final Window window = mWindow.getWindow();
             {
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 2484cf0..e5c22e4 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 
+import android.animation.ValueAnimator;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +29,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -44,6 +46,8 @@
 import android.view.WindowInsets;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManagerPolicyConstants;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 
 import java.util.Objects;
@@ -62,6 +66,9 @@
                 @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
         }
 
+        default void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+        }
+
         default void onViewInitialized() {
         }
 
@@ -71,7 +78,8 @@
         default void onDestroy() {
         }
 
-        default void onSystemBarAppearanceChanged(@Appearance int appearance) {
+        default void setShouldShowImeSwitcherWhenImeIsShown(
+                boolean shouldShowImeSwitcherWhenImeIsShown) {
         }
 
         default String toDebugString() {
@@ -94,6 +102,10 @@
         mImpl.updateTouchableInsets(originalInsets, dest);
     }
 
+    void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+        mImpl.onSoftInputWindowCreated(softInputWindow);
+    }
+
     void onViewInitialized() {
         mImpl.onViewInitialized();
     }
@@ -106,15 +118,21 @@
         mImpl.onDestroy();
     }
 
-    void onSystemBarAppearanceChanged(@Appearance int appearance) {
-        mImpl.onSystemBarAppearanceChanged(appearance);
+    void setShouldShowImeSwitcherWhenImeIsShown(boolean shouldShowImeSwitcherWhenImeIsShown) {
+        mImpl.setShouldShowImeSwitcherWhenImeIsShown(shouldShowImeSwitcherWhenImeIsShown);
     }
 
     String toDebugString() {
         return mImpl.toDebugString();
     }
 
-    private static final class Impl implements Callback {
+    private static final class Impl implements Callback, Window.DecorCallback {
+        private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
+
+        // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
+        private static final Interpolator LEGACY_DECELERATE =
+                new PathInterpolator(0f, 0f, 0.2f, 1f);
+
         @NonNull
         private final InputMethodService mService;
 
@@ -130,9 +148,19 @@
         @Nullable
         private BroadcastReceiver mSystemOverlayChangedReceiver;
 
+        private boolean mShouldShowImeSwitcherWhenImeIsShown;
+
         @Appearance
         private int mAppearance;
 
+        @FloatRange(from = 0.0f, to = 1.0f)
+        private float mDarkIntensity;
+
+        @Nullable
+        private ValueAnimator mTintAnimator;
+
+        private boolean mDrawLegacyNavigationBarBackground;
+
         Impl(@NonNull InputMethodService inputMethodService) {
             mService = inputMethodService;
         }
@@ -190,7 +218,9 @@
                     // TODO(b/213337792): Support InputMethodService#setBackDisposition().
                     // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
                     final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
-                            | StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+                            | (mShouldShowImeSwitcherWhenImeIsShown
+                                    ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN
+                                    : 0);
                     navigationBarView.setNavigationIconHints(hints);
                 }
             } else {
@@ -199,9 +229,14 @@
                 mLastInsets = systemInsets;
             }
 
-            mNavigationBarFrame.setBackground(null);
+            if (mDrawLegacyNavigationBarBackground) {
+                mNavigationBarFrame.setBackgroundColor(Color.BLACK);
+            } else {
+                mNavigationBarFrame.setBackground(null);
+            }
 
-            setIconTintInternal(calculateTargetDarkIntensity(mAppearance));
+            setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
+                    mDrawLegacyNavigationBarBackground));
         }
 
         private void uninstallNavigationBarFrameIfNecessary() {
@@ -335,6 +370,13 @@
         }
 
         @Override
+        public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+            final Window window = softInputWindow.getWindow();
+            mAppearance = window.getSystemBarAppearance();
+            window.setDecorCallback(this);
+        }
+
+        @Override
         public void onViewInitialized() {
             if (mDestroyed) {
                 return;
@@ -368,6 +410,10 @@
             if (mDestroyed) {
                 return;
             }
+            if (mTintAnimator != null) {
+                mTintAnimator.cancel();
+                mTintAnimator = null;
+            }
             if (mSystemOverlayChangedReceiver != null) {
                 mService.unregisterReceiver(mSystemOverlayChangedReceiver);
                 mSystemOverlayChangedReceiver = null;
@@ -404,6 +450,31 @@
         }
 
         @Override
+        public void setShouldShowImeSwitcherWhenImeIsShown(
+                boolean shouldShowImeSwitcherWhenImeIsShown) {
+            if (mDestroyed) {
+                return;
+            }
+            if (mShouldShowImeSwitcherWhenImeIsShown == shouldShowImeSwitcherWhenImeIsShown) {
+                return;
+            }
+            mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+
+            if (mNavigationBarFrame == null) {
+                return;
+            }
+            final NavigationBarView navigationBarView =
+                    mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
+            if (navigationBarView == null) {
+                return;
+            }
+            final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
+                    | (shouldShowImeSwitcherWhenImeIsShown
+                    ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN : 0);
+            navigationBarView.setNavigationIconHints(hints);
+        }
+
+        @Override
         public void onSystemBarAppearanceChanged(@Appearance int appearance) {
             if (mDestroyed) {
                 return;
@@ -415,11 +486,26 @@
                 return;
             }
 
-            final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance);
-            setIconTintInternal(targetDarkIntensity);
+            final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance,
+                    mDrawLegacyNavigationBarBackground);
+
+            if (mTintAnimator != null) {
+                mTintAnimator.cancel();
+            }
+            mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
+            mTintAnimator.addUpdateListener(
+                    animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
+            mTintAnimator.setDuration(DEFAULT_COLOR_ADAPT_TRANSITION_TIME);
+            mTintAnimator.setStartDelay(0);
+            mTintAnimator.setInterpolator(LEGACY_DECELERATE);
+            mTintAnimator.start();
         }
 
         private void setIconTintInternal(float darkIntensity) {
+            mDarkIntensity = darkIntensity;
+            if (mNavigationBarFrame == null) {
+                return;
+            }
             final NavigationBarView navigationBarView =
                     mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
             if (navigationBarView == null) {
@@ -429,16 +515,41 @@
         }
 
         @FloatRange(from = 0.0f, to = 1.0f)
-        private static float calculateTargetDarkIntensity(@Appearance int appearance) {
-            final boolean lightNavBar = (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
+        private static float calculateTargetDarkIntensity(@Appearance int appearance,
+                boolean drawLegacyNavigationBarBackground) {
+            final boolean lightNavBar = !drawLegacyNavigationBarBackground
+                    && (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
             return lightNavBar ? 1.0f : 0.0f;
         }
 
         @Override
+        public boolean onDrawLegacyNavigationBarBackgroundChanged(
+                boolean drawLegacyNavigationBarBackground) {
+            if (mDestroyed) {
+                return false;
+            }
+
+            if (drawLegacyNavigationBarBackground != mDrawLegacyNavigationBarBackground) {
+                mDrawLegacyNavigationBarBackground = drawLegacyNavigationBarBackground;
+                if (mDrawLegacyNavigationBarBackground) {
+                    mNavigationBarFrame.setBackgroundColor(Color.BLACK);
+                } else {
+                    mNavigationBarFrame.setBackground(null);
+                }
+                scheduleRelayout();
+                onSystemBarAppearanceChanged(mAppearance);
+            }
+            return drawLegacyNavigationBarBackground;
+        }
+
+        @Override
         public String toDebugString() {
             return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
                     + " mNavigationBarFrame=" + mNavigationBarFrame
+                    + " mShouldShowImeSwitcherWhenImeIsShown" + mShouldShowImeSwitcherWhenImeIsShown
                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
+                    + " mDarkIntensity=" + mDarkIntensity
+                    + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
                     + "}";
         }
     }
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 0893d2a..5704dac 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -31,7 +31,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.WindowInsetsController;
 import android.view.WindowManager;
 
 import java.lang.annotation.Retention;
@@ -264,11 +263,6 @@
         }
     }
 
-    @Override
-    public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
-        mService.mNavigationBarController.onSystemBarAppearanceChanged(appearance);
-    }
-
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         mBounds.dumpDebug(proto, BOUNDS);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index c936bfa..9122adf 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -523,9 +523,11 @@
      *
      * @param subId the subscriber to get the subscription plans for.
      * @param callingPackage the name of the package making the call.
+     * @return the active {@link SubscriptionPlan}s for the given subscription id, or
+     *         {@code null} if not found.
      * @hide
      */
-    @NonNull
+    @Nullable
     public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) {
         try {
             return mService.getSubscriptionPlans(subId, callingPackage);
@@ -538,7 +540,7 @@
      * Get subscription plan for the given networkTemplate.
      *
      * @param template the networkTemplate to get the subscription plan for.
-     * @return the active {@link SubscriptionPlan} for the given template, or
+     * @return the active {@link SubscriptionPlan}s for the given template, or
      *         {@code null} if not found.
      * @hide
      */
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index 24c22a9..9772bde 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -16,9 +16,7 @@
 
 package android.net.netstats;
 
-import static android.app.usage.NetworkStatsManager.PREFIX_UID;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG;
-import static android.app.usage.NetworkStatsManager.PREFIX_XT;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
@@ -28,6 +26,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.net.NetworkIdentity;
 import android.net.NetworkStatsCollection;
 import android.net.NetworkStatsHistory;
@@ -54,12 +53,27 @@
 import java.util.Set;
 
 /**
- * Helper class to read old version of persistent network statistics, the implementation is
- * intended to be modified by OEM partners to accommodate their custom changes.
+ * Helper class to read old version of persistent network statistics.
+ *
+ * The implementation is intended to be modified by OEM partners to
+ * accommodate their custom changes.
+ *
  * @hide
  */
-// @SystemApi(client = MODULE_LIBRARIES)
+@SystemApi(client = MODULE_LIBRARIES)
 public class NetworkStatsDataMigrationUtils {
+    /**
+     * Prefix of the files which are used to store per network interface statistics.
+     */
+    public static final String PREFIX_XT = "xt";
+    /**
+     * Prefix of the files which are used to store per uid statistics.
+     */
+    public static final String PREFIX_UID = "uid";
+    /**
+     * Prefix of the files which are used to store per uid tagged traffic statistics.
+     */
+    public static final String PREFIX_UID_TAG = "uid_tag";
 
     private static final HashMap<String, String> sPrefixLegacyFileNameMap =
             new HashMap<String, String>() {{
@@ -146,17 +160,51 @@
     }
 
     /**
-     * Read legacy persisted network stats from disk. This function provides a default
-     * implementation to read persisted network stats from two different locations.
-     * And this is intended to be modified by OEM to read from custom file format or
-     * locations if necessary.
+     * Read legacy persisted network stats from disk.
+     *
+     * This function provides the implementation to read legacy network stats
+     * from disk. It is used for migration of legacy network stats into the
+     * stats provided by the Connectivity module.
+     * This function needs to know about the previous format(s) of the network
+     * stats data that might be stored on this device so it can be read and
+     * conserved upon upgrade to Android 13 or above.
+     *
+     * This function will be called multiple times sequentially, all on the
+     * same thread, and will not be called multiple times concurrently. This
+     * function is expected to do a substantial amount of disk access, and
+     * doesn't need to return particularly fast, but the first boot after
+     * an upgrade to Android 13+ will be held until migration is done. As
+     * migration is only necessary once, after the first boot following the
+     * upgrade, this delay is not incurred.
+     *
+     * If this function fails in any way, it should throw an exception. If this
+     * happens, the system can't know about the data that was stored in the
+     * legacy files, but it will still count data usage happening on this
+     * session. On the next boot, the system will try migration again, and
+     * merge the returned data with the data used with the previous session.
+     * The system will only try the migration up to three (3) times. The remaining
+     * count is stored in the netstats_import_legacy_file_needed device config. The
+     * legacy data is never deleted by the mainline module to avoid any possible
+     * data loss.
+     *
+     * It is possible to set the netstats_import_legacy_file_needed device config
+     * to any positive integer to force the module to perform the migration. This
+     * can be achieved by calling the following command before rebooting :
+     *     adb shell device_config put connectivity netstats_import_legacy_file_needed 1
+     *
+     * The AOSP implementation provides code to read persisted network stats as
+     * they were written by AOSP prior to Android 13.
+     * OEMs who have used the AOSP implementation of persisting network stats
+     * to disk don't need to change anything.
+     * OEM that had modifications to this format should modify this function
+     * to read from their custom file format or locations if necessary.
      *
      * @param prefix         Type of data which is being read by the service.
      * @param bucketDuration Duration of the buckets of the object, in milliseconds.
      * @return {@link NetworkStatsCollection} instance.
      */
     @NonNull
-    public static NetworkStatsCollection readPlatformCollectionLocked(
+    public static NetworkStatsCollection readPlatformCollection(
             @NonNull String prefix, long bucketDuration) throws IOException {
         final NetworkStatsCollection.Builder builder =
                 new NetworkStatsCollection.Builder(bucketDuration);
@@ -397,7 +445,7 @@
             if (version >= IdentitySetVersion.VERSION_ADD_OEM_MANAGED_NETWORK) {
                 oemNetCapabilities = in.readInt();
             } else {
-                oemNetCapabilities = NetworkIdentity.OEM_NONE;
+                oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO;
             }
 
             // Legacy files might contain TYPE_MOBILE_* types which were deprecated in later
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2d33817..07a5132 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -34,6 +34,7 @@
 import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
 import android.service.batterystats.BatteryStatsServiceDumpProto;
 import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
@@ -2654,6 +2655,46 @@
      */
     public abstract Timer getPhoneDataConnectionTimer(int dataType);
 
+    /** @hide */
+    public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0;
+    /** @hide */
+    public static final int RADIO_ACCESS_TECHNOLOGY_LTE = 1;
+    /** @hide */
+    public static final int RADIO_ACCESS_TECHNOLOGY_NR = 2;
+    /** @hide */
+    public static final int RADIO_ACCESS_TECHNOLOGY_COUNT = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "RADIO_ACCESS_TECHNOLOGY_",
+            value = {RADIO_ACCESS_TECHNOLOGY_OTHER, RADIO_ACCESS_TECHNOLOGY_LTE,
+                    RADIO_ACCESS_TECHNOLOGY_NR})
+    public @interface RadioAccessTechnology {
+    }
+
+    /** @hide */
+    public static final String[] RADIO_ACCESS_TECHNOLOGY_NAMES = {"Other", "LTE", "NR"};
+
+    /**
+     * Returns the time in microseconds that the mobile radio has been active on a
+     * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+     * transmission power level.
+     *
+     * @param rat            Radio Access Technology {@see RadioAccessTechnology}
+     * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+     *                       RADIO_ACCESS_TECHNOLOGY_NR. Use
+     *                       {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+     *                       Technologies.
+     * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()}
+     * @param elapsedRealtimeMs current elapsed realtime
+     * @return time (in milliseconds) the mobile radio spent active in the specified state,
+     *         while on battery.
+     * @hide
+     */
+    public abstract long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+            long elapsedRealtimeMs);
+
     static final String[] WIFI_SUPPL_STATE_NAMES = {
         "invalid", "disconn", "disabled", "inactive", "scanning",
         "authenticating", "associating", "associated", "4-way-handshake",
@@ -3997,6 +4038,89 @@
         }
     }
 
+    private void printCellularPerRatBreakdown(PrintWriter pw, StringBuilder sb, String prefix,
+            long rawRealtimeMs) {
+        final String allFrequenciesHeader =
+                "    All frequencies:\n";
+        final String[] nrFrequencyRangeDescription = new String[]{
+                "    Unknown frequency:\n",
+                "    Low frequency (less than 1GHz):\n",
+                "    Middle frequency (1GHz to 3GHz):\n",
+                "    High frequency (3GHz to 6GHz):\n",
+                "    Mmwave frequency (greater than 6GHz):\n"};
+        final String signalStrengthHeader =
+                "      Signal Strength Time:\n";
+        final String[] signalStrengthDescription = new String[]{
+                "        unknown:  ",
+                "        poor:     ",
+                "        moderate: ",
+                "        good:     ",
+                "        great:    "};
+
+        final long totalActiveTimesMs = getMobileRadioActiveTime(rawRealtimeMs * 1000,
+                STATS_SINCE_CHARGED) / 1000;
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("Active Cellular Radio Access Technology Breakdown:");
+        pw.println(sb);
+
+        boolean hasData = false;
+        final int numSignalStrength = CellSignalStrength.getNumSignalStrengthLevels();
+        for (int rat = RADIO_ACCESS_TECHNOLOGY_COUNT - 1; rat >= 0; rat--) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  ");
+            sb.append(RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+            sb.append(":\n");
+            sb.append(prefix);
+
+            final int numFreqLvl =
+                    rat == RADIO_ACCESS_TECHNOLOGY_NR ? nrFrequencyRangeDescription.length : 1;
+            for (int freqLvl = numFreqLvl - 1; freqLvl >= 0; freqLvl--) {
+                final int freqDescriptionStart = sb.length();
+                boolean hasFreqData = false;
+                if (rat == RADIO_ACCESS_TECHNOLOGY_NR) {
+                    sb.append(nrFrequencyRangeDescription[freqLvl]);
+                } else {
+                    sb.append(allFrequenciesHeader);
+                }
+
+                sb.append(prefix);
+                sb.append(signalStrengthHeader);
+                for (int strength = 0; strength < numSignalStrength; strength++) {
+                    final long timeMs = getActiveRadioDurationMs(rat, freqLvl, strength,
+                            rawRealtimeMs);
+                    if (timeMs <= 0) continue;
+                    hasFreqData = true;
+                    sb.append(prefix);
+                    sb.append(signalStrengthDescription[strength]);
+                    formatTimeMs(sb, timeMs);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(timeMs, totalActiveTimesMs));
+                    sb.append(")\n");
+                }
+
+                if (hasFreqData) {
+                    hasData = true;
+                    pw.print(sb);
+                    sb.setLength(0);
+                    sb.append(prefix);
+                } else {
+                    // No useful data was printed, rewind sb to before the start of this frequency.
+                    sb.setLength(freqDescriptionStart);
+                }
+            }
+        }
+
+        if (!hasData) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  (no activity)");
+            pw.println(sb);
+        }
+    }
+
     /**
      * Temporary for settings.
      */
@@ -5269,6 +5393,8 @@
         printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME,
                 getModemControllerActivity(), which);
 
+        printCellularPerRatBreakdown(pw, sb, prefix + "     ", rawRealtimeMs);
+
         pw.print("     Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
         pw.print("     Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
         pw.print("     Cellular packets received: "); pw.println(mobileRxTotalPackets);
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index 2a609b8..f16bbc6 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -24,6 +24,8 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.content.Context;
 import android.net.NetworkStack;
 import android.os.connectivity.CellularBatteryStats;
@@ -515,6 +517,42 @@
     }
 
     /**
+     * Indicates that Bluetooth was toggled on.
+     *
+     * @param uid calling package uid
+     * @param reason why Bluetooth has been turned on
+     * @param packageName package responsible for this change
+     */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) {
+        try {
+            mBatteryStats.noteBluetoothOn(uid, reason, packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates that Bluetooth was toggled off.
+     *
+     * @param uid calling package uid
+     * @param reason why Bluetooth has been turned on
+     * @param packageName package responsible for this change
+     */
+    @RequiresLegacyBluetoothAdminPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) {
+        try {
+            mBatteryStats.noteBluetoothOff(uid, reason, packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Indicates that a new Bluetooth LE scan has started.
      *
      * @param ws worksource (to be used for battery blaming).
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 1b7c00c..6330661 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -526,12 +526,15 @@
     public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
         Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
 
-        if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)
+        boolean warnOnBlocking = mWarnOnBlocking; // Cache it to reduce volatile access.
+
+        if (warnOnBlocking && ((flags & FLAG_ONEWAY) == 0)
                 && Binder.sWarnOnBlockingOnCurrentThread.get()) {
 
             // For now, avoid spamming the log by disabling after we've logged
             // about this interface at least once
             mWarnOnBlocking = false;
+            warnOnBlocking = false;
 
             if (Build.IS_USERDEBUG) {
                 // Log this as a WTF on userdebug builds.
@@ -578,7 +581,13 @@
         }
 
         try {
-            return transactNative(code, data, reply, flags);
+            final boolean result = transactNative(code, data, reply, flags);
+
+            if (reply != null && !warnOnBlocking) {
+                reply.addFlags(Parcel.FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT);
+            }
+
+            return result;
         } finally {
             AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
 
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 35b9ccc..9970641 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -31,6 +31,7 @@
 import android.sysprop.SocProperties;
 import android.sysprop.TelephonyProperties;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.View;
 
@@ -39,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -396,6 +398,17 @@
          */
         public static final String CODENAME = getString("ro.build.version.codename");
 
+        /**
+         * All known codenames starting from {@link VERSION_CODES.Q}.
+         *
+         * <p>This includes in development codenames as well.
+         *
+         * @hide
+         */
+        @SystemApi
+        @NonNull public static final Set<String> KNOWN_CODENAMES =
+                new ArraySet<>(new String[]{"Q", "R", "S", "Sv2", "Tiramisu"});
+
         private static final String[] ALL_CODENAMES
                 = getStringList("ro.build.version.all_codenames", ",");
 
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index aa4b83a..0c3514f 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -165,7 +165,7 @@
     private boolean isAngleEnabledByGameMode(Context context, String packageName) {
         try {
             final boolean gameModeEnabledAngle =
-                    (mGameManager != null) && mGameManager.getAngleEnabled(packageName);
+                    (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
             Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
             return gameModeEnabledAngle;
         } catch (SecurityException e) {
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index df5b7bc..4fe6524 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -21,15 +21,16 @@
 import android.os.ParcelDuration;
 import android.os.PowerSaveState;
 import android.os.WorkSource;
+import android.os.IWakeLockCallback;
 
 /** @hide */
 
 interface IPowerManager
 {
     void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws,
-            String historyTag, int displayId);
+            String historyTag, int displayId, IWakeLockCallback callback);
     void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName,
-            int uidtoblame, int displayId);
+            int uidtoblame, int displayId, IWakeLockCallback callback);
     @UnsupportedAppUsage
     void releaseWakeLock(IBinder lock, int flags);
     void updateWakeLockUids(IBinder lock, in int[] uids);
@@ -40,6 +41,7 @@
     boolean setPowerModeChecked(int mode, boolean enabled);
 
     void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag);
+    void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback);
     boolean isWakeLockLevelSupported(int level);
 
     void userActivity(int displayId, long time, int event, int flags);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/os/IWakeLockCallback.aidl
similarity index 82%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/os/IWakeLockCallback.aidl
index 861a4ed..89615d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/os/IWakeLockCallback.aidl
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.mediattt;
+package android.os;
 
-parcelable DeviceInfo;
+/**
+ * @hide
+ */
+oneway interface IWakeLockCallback {
+    oneway void onStateChanged(boolean enabled);
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3bc3ec8..9998e12 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -18,6 +18,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -53,6 +54,8 @@
 import java.io.ObjectOutputStream;
 import java.io.ObjectStreamClass;
 import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
@@ -229,6 +232,25 @@
 
     private RuntimeException mStack;
 
+    /** @hide */
+    @TestApi
+    public static final int FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT = 1 << 0;
+
+    /** @hide */
+    @TestApi
+    public static final int FLAG_PROPAGATE_ALLOW_BLOCKING = 1 << 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+            FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT,
+            FLAG_PROPAGATE_ALLOW_BLOCKING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ParcelFlags {}
+
+    @ParcelFlags
+    private int mFlags;
+
     /**
      * Whether or not to parcel the stack trace of an exception. This has a performance
      * impact, so should only be included in specific processes and only on debug builds.
@@ -585,6 +607,40 @@
         nativeMarkForBinder(mNativePtr, binder);
     }
 
+    /** @hide */
+    @ParcelFlags
+    @TestApi
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /** @hide */
+    public void setFlags(@ParcelFlags int flags) {
+        mFlags = flags;
+    }
+
+    /** @hide */
+    public void addFlags(@ParcelFlags int flags) {
+        mFlags |= flags;
+    }
+
+    /** @hide */
+    private boolean hasFlags(@ParcelFlags int flags) {
+        return (mFlags & flags) == flags;
+    }
+
+    /**
+     * This method is used by the AIDL compiler for system components. Not intended to be
+     * used by non-system apps.
+     */
+    // Note: Ideally this method should be @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES),
+    // but we need to make this method public due to the way the aidl compiler is compiled.
+    // We don't really need to protect it; even if 3p / non-system apps, nothing would happen.
+    // This would only work when used on a reply parcel by a binder object that's allowed-blocking.
+    public void setPropagateAllowBlocking() {
+        addFlags(FLAG_PROPAGATE_ALLOW_BLOCKING);
+    }
+
     /**
      * Returns the total amount of data contained in the parcel.
      */
@@ -2088,6 +2144,102 @@
     }
 
     /**
+     * Flatten a homogeneous multi-dimensional array with fixed-size.  This delegates to other
+     * APIs to write a one-dimensional array.  Use {@link #readFixedArray(Object)} or
+     * {@link #createFixedArray(Class, int[])} with the same dimensions to unmarshal.
+     *
+     * @param val The array to be written.
+     * @param parcelableFlags Contextual flags as per
+     *   {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+     *   Used only if val is an array of Parcelable objects.
+     * @param dimensions an array of int representing length of each dimension. The array should be
+     *   sized with the exact size of dimensions.
+     *
+     * @see #readFixedArray
+     * @see #createFixedArray
+     * @see #writeBooleanArray
+     * @see #writeByteArray
+     * @see #writeCharArray
+     * @see #writeIntArray
+     * @see #writeLongArray
+     * @see #writeFloatArray
+     * @see #writeDoubleArray
+     * @see #writeBinderArray
+     * @see #writeInterfaceArray
+     * @see #writeTypedArray
+     * @throws BadParcelableException If the array's component type is not supported or if its
+     *   size doesn't match with the given dimensions.
+     */
+    public <T> void writeFixedArray(@Nullable T val, int parcelableFlags,
+            @NonNull int... dimensions) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        writeFixedArrayInternal(val, parcelableFlags, /*index=*/0, dimensions);
+    }
+
+    private <T> void writeFixedArrayInternal(T val, int parcelableFlags, int index,
+            int[] dimensions) {
+        if (index >= dimensions.length) {
+            throw new BadParcelableException("Array has more dimensions than expected: "
+                + dimensions.length);
+        }
+
+        int length = dimensions[index];
+
+        // val should be an array of length N
+        if (val == null) {
+            throw new BadParcelableException("Non-null array shouldn't have a null array.");
+        }
+        if (!val.getClass().isArray()) {
+            throw new BadParcelableException("Not an array: " + val);
+        }
+        if (Array.getLength(val) != length) {
+            throw new BadParcelableException("bad length: expected " + length + ", but got "
+                + Array.getLength(val));
+        }
+
+        // Delegates to other writers if this is a one-dimensional array.
+        // Otherwise, write component arrays with recursive calls.
+
+        final Class<?> componentType = val.getClass().getComponentType();
+        if (!componentType.isArray() && index + 1 != dimensions.length) {
+            throw new BadParcelableException("Array has fewer dimensions than expected: "
+                + dimensions.length);
+        }
+        if (componentType == boolean.class) {
+            writeBooleanArray((boolean[]) val);
+        } else if (componentType == byte.class) {
+            writeByteArray((byte[]) val);
+        } else if (componentType == char.class) {
+            writeCharArray((char[]) val);
+        } else if (componentType == int.class) {
+            writeIntArray((int[]) val);
+        } else if (componentType == long.class) {
+            writeLongArray((long[]) val);
+        } else if (componentType == float.class) {
+            writeFloatArray((float[]) val);
+        } else if (componentType == double.class) {
+            writeDoubleArray((double[]) val);
+        } else if (componentType == IBinder.class) {
+            writeBinderArray((IBinder[]) val);
+        } else if (IInterface.class.isAssignableFrom(componentType)) {
+            writeInterfaceArray((IInterface[]) val);
+        } else if (Parcelable.class.isAssignableFrom(componentType)) {
+            writeTypedArray((Parcelable[]) val, parcelableFlags);
+        } else if (componentType.isArray()) {
+            writeInt(length);
+            for (int i = 0; i < length; i++) {
+                writeFixedArrayInternal(Array.get(val, i), parcelableFlags, index + 1,
+                        dimensions);
+            }
+        } else {
+            throw new BadParcelableException("unknown type for fixed-size array: " + componentType);
+        }
+    }
+
+    /**
      * Flatten a generic object in to a parcel.  The given Object value may
      * currently be one of the following types:
      *
@@ -2949,7 +3101,15 @@
      * Read an object from the parcel at the current dataPosition().
      */
     public final IBinder readStrongBinder() {
-        return nativeReadStrongBinder(mNativePtr);
+        final IBinder result = nativeReadStrongBinder(mNativePtr);
+
+        // If it's a reply from a method with @PropagateAllowBlocking, then inherit allow-blocking
+        // from the object that returned it.
+        if (result != null && hasFlags(
+                FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT | FLAG_PROPAGATE_ALLOW_BLOCKING)) {
+            Binder.allowBlocking(result);
+        }
+        return result;
     }
 
     /**
@@ -3788,6 +3948,317 @@
     }
 
     /**
+     * Read a new multi-dimensional array from a parcel.  If you want to read Parcelable or
+     * IInterface values, use {@link #readFixedArray(Object, Parcelable.Creator)} or
+     * {@link #readFixedArray(Object, Function)}.
+     * @param val the destination array to hold the read values.
+     *
+     * @see #writeTypedArray
+     * @see #readBooleanArray
+     * @see #readByteArray
+     * @see #readCharArray
+     * @see #readIntArray
+     * @see #readLongArray
+     * @see #readFloatArray
+     * @see #readDoubleArray
+     * @see #readBinderArray
+     * @see #readInterfaceArray
+     * @see #readTypedArray
+     */
+    public <T> void readFixedArray(@NonNull T val) {
+        Class<?> componentType = val.getClass().getComponentType();
+        if (componentType == boolean.class) {
+            readBooleanArray((boolean[]) val);
+        } else if (componentType == byte.class) {
+            readByteArray((byte[]) val);
+        } else if (componentType == char.class) {
+            readCharArray((char[]) val);
+        } else if (componentType == int.class) {
+            readIntArray((int[]) val);
+        } else if (componentType == long.class) {
+            readLongArray((long[]) val);
+        } else if (componentType == float.class) {
+            readFloatArray((float[]) val);
+        } else if (componentType == double.class) {
+            readDoubleArray((double[]) val);
+        } else if (componentType == IBinder.class) {
+            readBinderArray((IBinder[]) val);
+        } else if (componentType.isArray()) {
+            int length = readInt();
+            if (length != Array.getLength(val)) {
+                throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+                    + ", but got " + length);
+            }
+            for (int i = 0; i < length; i++) {
+                readFixedArray(Array.get(val, i));
+            }
+        } else {
+            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+        }
+    }
+
+    /**
+     * Read a new multi-dimensional array of typed interfaces from a parcel.
+     * If you want to read Parcelable values, use
+     * {@link #readFixedArray(Object, Parcelable.Creator)}. For values of other types, use
+     * {@link #readFixedArray(Object)}.
+     * @param val the destination array to hold the read values.
+     */
+    public <T, S extends IInterface> void readFixedArray(@NonNull T val,
+            @NonNull Function<IBinder, S> asInterface) {
+        Class<?> componentType = val.getClass().getComponentType();
+        if (IInterface.class.isAssignableFrom(componentType)) {
+            readInterfaceArray((S[]) val, asInterface);
+        } else if (componentType.isArray()) {
+            int length = readInt();
+            if (length != Array.getLength(val)) {
+                throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+                    + ", but got " + length);
+            }
+            for (int i = 0; i < length; i++) {
+                readFixedArray(Array.get(val, i), asInterface);
+            }
+        } else {
+            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+        }
+    }
+
+    /**
+     * Read a new multi-dimensional array of typed parcelables from a parcel.
+     * If you want to read IInterface values, use
+     * {@link #readFixedArray(Object, Function)}. For values of other types, use
+     * {@link #readFixedArray(Object)}.
+     * @param val the destination array to hold the read values.
+     */
+    public <T, S extends Parcelable> void readFixedArray(@NonNull T val,
+            @NonNull Parcelable.Creator<S> c) {
+        Class<?> componentType = val.getClass().getComponentType();
+        if (Parcelable.class.isAssignableFrom(componentType)) {
+            readTypedArray((S[]) val, c);
+        } else if (componentType.isArray()) {
+            int length = readInt();
+            if (length != Array.getLength(val)) {
+                throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+                    + ", but got " + length);
+            }
+            for (int i = 0; i < length; i++) {
+                readFixedArray(Array.get(val, i), c);
+            }
+        } else {
+            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+        }
+    }
+
+    private void ensureClassHasExpectedDimensions(@NonNull Class<?> cls, int numDimension) {
+        if (numDimension <= 0) {
+            throw new BadParcelableException("Fixed-size array should have dimensions.");
+        }
+
+        for (int i = 0; i < numDimension; i++) {
+            if (!cls.isArray()) {
+                throw new BadParcelableException("Array has fewer dimensions than expected: "
+                    + numDimension);
+            }
+            cls = cls.getComponentType();
+        }
+        if (cls.isArray()) {
+            throw new BadParcelableException("Array has more dimensions than expected: "
+                + numDimension);
+        }
+    }
+
+    /**
+     * Read and return a new multi-dimensional array from a parcel.  Returns null if the
+     * previously written array object is null.  If you want to read Parcelable or
+     * IInterface values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])} or
+     * {@link #createFixedArray(Class, Function, int[])}.
+     * @param cls  the Class object for the target array type. (e.g. int[][].class)
+     * @param dimensions an array of int representing length of each dimension.
+     *
+     * @see #writeTypedArray
+     * @see #createBooleanArray
+     * @see #createByteArray
+     * @see #createCharArray
+     * @see #createIntArray
+     * @see #createLongArray
+     * @see #createFloatArray
+     * @see #createDoubleArray
+     * @see #createBinderArray
+     * @see #createInterfaceArray
+     * @see #createTypedArray
+     */
+    @Nullable
+    public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) {
+        // Check if type matches with dimensions
+        // If type is one-dimensional array, delegate to other creators
+        // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+        ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+        T val = null;
+        final Class<?> componentType = cls.getComponentType();
+        if (componentType == boolean.class) {
+            val = (T) createBooleanArray();
+        } else if (componentType == byte.class) {
+            val = (T) createByteArray();
+        } else if (componentType == char.class) {
+            val = (T) createCharArray();
+        } else if (componentType == int.class) {
+            val = (T) createIntArray();
+        } else if (componentType == long.class) {
+            val = (T) createLongArray();
+        } else if (componentType == float.class) {
+            val = (T) createFloatArray();
+        } else if (componentType == double.class) {
+            val = (T) createDoubleArray();
+        } else if (componentType == IBinder.class) {
+            val = (T) createBinderArray();
+        } else if (componentType.isArray()) {
+            int length = readInt();
+            if (length < 0) {
+                return null;
+            }
+            if (length != dimensions[0]) {
+                throw new BadParcelableException("Bad length: expected " + dimensions[0]
+                    + ", but got " + length);
+            }
+
+            // Create a multi-dimensional array with an innermost component type and dimensions
+            Class<?> innermost = componentType.getComponentType();
+            while (innermost.isArray()) {
+                innermost = innermost.getComponentType();
+            }
+            val = (T) Array.newInstance(innermost, dimensions);
+            for (int i = 0; i < length; i++) {
+                readFixedArray(Array.get(val, i));
+            }
+            return val;
+        } else {
+            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+        }
+
+        // Check if val is null (which is OK) or has the expected size.
+        // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+        // are created with expected dimensions.
+        if (val != null && Array.getLength(val) != dimensions[0]) {
+            throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+                + Array.getLength(val));
+        }
+        return val;
+    }
+
+    /**
+     * Read and return a new multi-dimensional array of typed interfaces from a parcel.
+     * Returns null if the previously written array object is null.  If you want to read
+     * Parcelable values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])}.
+     * For values of other types use {@link #createFixedArray(Class, int[])}.
+     * @param cls  the Class object for the target array type. (e.g. IFoo[][].class)
+     * @param dimensions an array of int representing length of each dimension.
+     */
+    @Nullable
+    public <T, S extends IInterface> T createFixedArray(@NonNull Class<T> cls,
+            @NonNull Function<IBinder, S> asInterface, @NonNull int... dimensions) {
+        // Check if type matches with dimensions
+        // If type is one-dimensional array, delegate to other creators
+        // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+        ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+        T val = null;
+        final Class<?> componentType = cls.getComponentType();
+        if (IInterface.class.isAssignableFrom(componentType)) {
+            val = (T) createInterfaceArray(n -> (S[]) Array.newInstance(componentType, n),
+                    asInterface);
+        } else if (componentType.isArray()) {
+            int length = readInt();
+            if (length < 0) {
+                return null;
+            }
+            if (length != dimensions[0]) {
+                throw new BadParcelableException("Bad length: expected " + dimensions[0]
+                    + ", but got " + length);
+            }
+
+            // Create a multi-dimensional array with an innermost component type and dimensions
+            Class<?> innermost = componentType.getComponentType();
+            while (innermost.isArray()) {
+                innermost = innermost.getComponentType();
+            }
+            val = (T) Array.newInstance(innermost, dimensions);
+            for (int i = 0; i < length; i++) {
+                readFixedArray(Array.get(val, i), asInterface);
+            }
+            return val;
+        } else {
+            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+        }
+
+        // Check if val is null (which is OK) or has the expected size.
+        // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+        // are created with expected dimensions.
+        if (val != null && Array.getLength(val) != dimensions[0]) {
+            throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+                + Array.getLength(val));
+        }
+        return val;
+    }
+
+    /**
+     * Read and return a new multi-dimensional array of typed parcelables from a parcel.
+     * Returns null if the previously written array object is null.  If you want to read
+     * IInterface values, use {@link #createFixedArray(Class, Function, int[])}.
+     * For values of other types use {@link #createFixedArray(Class, int[])}.
+     * @param cls  the Class object for the target array type. (e.g. Foo[][].class)
+     * @param dimensions an array of int representing length of each dimension.
+     */
+    @Nullable
+    public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls,
+            @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) {
+        // Check if type matches with dimensions
+        // If type is one-dimensional array, delegate to other creators
+        // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+        ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+        T val = null;
+        final Class<?> componentType = cls.getComponentType();
+        if (Parcelable.class.isAssignableFrom(componentType)) {
+            val = (T) createTypedArray(c);
+        } else if (componentType.isArray()) {
+            int length = readInt();
+            if (length < 0) {
+                return null;
+            }
+            if (length != dimensions[0]) {
+                throw new BadParcelableException("Bad length: expected " + dimensions[0]
+                    + ", but got " + length);
+            }
+
+            // Create a multi-dimensional array with an innermost component type and dimensions
+            Class<?> innermost = componentType.getComponentType();
+            while (innermost.isArray()) {
+                innermost = innermost.getComponentType();
+            }
+            val = (T) Array.newInstance(innermost, dimensions);
+            for (int i = 0; i < length; i++) {
+                readFixedArray(Array.get(val, i), c);
+            }
+            return val;
+        } else {
+            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+        }
+
+        // Check if val is null (which is OK) or has the expected size.
+        // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+        // are created with expected dimensions.
+        if (val != null && Array.getLength(val) != dimensions[0]) {
+            throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+                + Array.getLength(val));
+        }
+        return val;
+    }
+
+    /**
      * Write a heterogeneous array of Parcelable objects into the Parcel.
      * Each object in the array is written along with its class name, so
      * that the correct class can later be instantiated.  As a result, this
@@ -4588,6 +5059,7 @@
     }
 
     private void freeBuffer() {
+        mFlags = 0;
         resetSqaushingState();
         if (mOwnsNativeParcelObject) {
             nativeFreeBuffer(mNativePtr);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 5bd8588..315eef7 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2770,6 +2770,23 @@
     public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
 
     /**
+     * A listener interface to get notified when the wakelock is enabled/disabled.
+     */
+    public interface WakeLockStateListener {
+        /**
+         * Frameworks could disable the wakelock because either device's power allowlist has
+         * changed, or the app's wakelock has exceeded its quota, or the app goes into cached
+         * state.
+         * <p>
+         * This callback is called whenever the wakelock's state has changed.
+         * </p>
+         *
+         * @param enabled true is enabled, false is disabled.
+         */
+        void onStateChanged(boolean enabled);
+    }
+
+    /**
      * A wake lock is a mechanism to indicate that your application needs
      * to have the device stay on.
      * <p>
@@ -2800,6 +2817,8 @@
         private String mHistoryTag;
         private final String mTraceName;
         private final int mDisplayId;
+        private WakeLockStateListener mListener;
+        private IWakeLockCallback mCallback;
 
         private final Runnable mReleaser = () -> release(RELEASE_FLAG_TIMEOUT);
 
@@ -2890,7 +2909,7 @@
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
                 try {
                     mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
-                            mHistoryTag, mDisplayId);
+                            mHistoryTag, mDisplayId, mCallback);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -3083,6 +3102,45 @@
                 }
             };
         }
+
+        /**
+         * Set the listener to get notified when the wakelock is enabled/disabled.
+         *
+         * @param executor {@link Executor} to handle listener callback.
+         * @param listener listener to be added, set the listener to null to cancel a listener.
+         */
+        public void setStateListener(@NonNull @CallbackExecutor Executor executor,
+                @Nullable WakeLockStateListener listener) {
+            Preconditions.checkNotNull(executor, "executor cannot be null");
+            synchronized (mToken) {
+                if (listener != mListener) {
+                    mListener = listener;
+                    if (listener != null) {
+                        mCallback = new IWakeLockCallback.Stub() {
+                            public void onStateChanged(boolean enabled) {
+                                final long token = Binder.clearCallingIdentity();
+                                try {
+                                    executor.execute(() -> {
+                                        listener.onStateChanged(enabled);
+                                    });
+                                } finally {
+                                    Binder.restoreCallingIdentity(token);
+                                }
+                            }
+                        };
+                    } else {
+                        mCallback = null;
+                    }
+                    if (mHeld) {
+                        try {
+                            mService.updateWakeLockCallback(mToken, mCallback);
+                        } catch (RemoteException e) {
+                            throw e.rethrowFromSystemServer();
+                        }
+                    }
+                }
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 49c0520..d223a19 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -106,20 +106,28 @@
     public static final int USAGE_NOTIFICATION = 0x30 | USAGE_CLASS_ALARM;
     /**
      * Usage value to use for vibrations which mean a request to enter/end a
-     * communication, such as a VoIP communication or video-conference.
+     * communication with the user, such as a voice prompt.
      */
     public static final int USAGE_COMMUNICATION_REQUEST = 0x40 | USAGE_CLASS_ALARM;
     /**
      * Usage value to use for touch vibrations.
+     *
+     * <p>Most typical haptic feedback should be classed as <em>touch</em> feedback. Examples
+     * include vibrations for tap, long press, drag and scroll.
      */
     public static final int USAGE_TOUCH = 0x10 | USAGE_CLASS_FEEDBACK;
     /**
-     * Usage value to use for vibrations which emulate physical effects, such as edge squeeze.
+     * Usage value to use for vibrations which emulate physical hardware reactions,
+     * such as edge squeeze.
+     *
+     * <p>Note that normal screen-touch feedback "click" effects would typically be
+     * classed as {@link #USAGE_TOUCH}, and that on-screen "physical" animations
+     * like bouncing would be {@link #USAGE_MEDIA}.
      */
     public static final int USAGE_PHYSICAL_EMULATION = 0x20 | USAGE_CLASS_FEEDBACK;
     /**
-     * Usage value to use for vibrations which provide a feedback for hardware interaction,
-     * such as a fingerprint sensor.
+     * Usage value to use for vibrations which provide a feedback for hardware
+     * component interaction, such as a fingerprint sensor.
      */
     public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK;
     /**
@@ -183,7 +191,6 @@
 
     /**
      * Return the vibration usage class.
-     * @return USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK or USAGE_CLASS_UNKNOWN
      */
     @UsageClass
     public int getUsageClass() {
@@ -192,7 +199,6 @@
 
     /**
      * Return the vibration usage.
-     * @return one of the values that can be set in {@link Builder#setUsage(int)}
      */
     @Usage
     public int getUsage() {
@@ -429,16 +435,8 @@
         }
 
         /**
-         * Sets the attribute describing the type of corresponding vibration.
-         * @param usage one of {@link VibrationAttributes#USAGE_ALARM},
-         * {@link VibrationAttributes#USAGE_RINGTONE},
-         * {@link VibrationAttributes#USAGE_NOTIFICATION},
-         * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST},
-         * {@link VibrationAttributes#USAGE_TOUCH},
-         * {@link VibrationAttributes#USAGE_PHYSICAL_EMULATION},
-         * {@link VibrationAttributes#USAGE_HARDWARE_FEEDBACK}.
-         * {@link VibrationAttributes#USAGE_ACCESSIBILITY}.
-         * {@link VibrationAttributes#USAGE_MEDIA}.
+         * Sets the attribute describing the type of the corresponding vibration.
+         * @param usage The type of usage for the vibration
          * @return the same Builder instance.
          */
         public @NonNull Builder setUsage(@Usage int usage) {
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index f490587..21c6487 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -194,27 +194,27 @@
     }
 
     /**
-     * Create a waveform vibration.
+     * Create a waveform vibration, using only off/on transitions at the provided time intervals,
+     * and potentially repeating.
      *
-     * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
-     * each pair, the value in the amplitude array determines the strength of the vibration and the
-     * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
-     * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+     * <p>In effect, the timings array represents the number of milliseconds <em>before</em> turning
+     * the vibrator on, followed by the number of milliseconds to keep the vibrator on, then
+     * the number of milliseconds turned off, and so on. Consequently, the first timing value will
+     * often be 0, so that the effect will start vibrating immediately.
      *
-     * <p>The amplitude array of the generated waveform will be the same size as the given
-     * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
-     * starting with 0. Therefore the first timing value will be the period to wait before turning
-     * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
-     * strength, etc.
+     * <p>This method is equivalent to calling {@link #createWaveform(long[], int[], int)} with
+     * corresponding amplitude values alternating between 0 and {@link #DEFAULT_AMPLITUDE},
+     * beginning with 0.
      *
      * <p>To cause the pattern to repeat, pass the index into the timings array at which to start
      * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
      * and should be cancelled via {@link Vibrator#cancel()}.
      *
-     * @param timings The pattern of alternating on-off timings, starting with off. Timing values
-     *                of 0 will cause the timing / amplitude pair to be ignored.
-     * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
-     *               want to repeat.
+     * @param timings The pattern of alternating on-off timings, starting with an 'off' timing, and
+     *               representing the length of time to sustain the individual item (not
+     *               cumulative).
+     * @param repeat The index into the timings array at which to repeat, or -1 if you don't
+     *               want to repeat indefinitely.
      *
      * @return The desired effect.
      */
@@ -229,11 +229,10 @@
     /**
      * Create a waveform vibration.
      *
-     * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
-     * each pair, the value in the amplitude array determines the strength of the vibration and the
-     * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude
-     * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any
-     * pairs with a timing value of 0 will be ignored.
+     * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
+     * provided in separate arrays. For each pair, the value in the amplitude array determines
+     * the strength of the vibration and the value in the timing array determines how long it
+     * vibrates for, in milliseconds.
      *
      * <p>To cause the pattern to repeat, pass the index into the timings array at which to start
      * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
@@ -244,8 +243,8 @@
      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
      *                   amplitude value of 0 implies the motor is off.
-     * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
-     *               want to repeat.
+     * @param repeat The index into the timings array at which to repeat, or -1 if you don't
+     *               want to repeat indefinitely.
      *
      * @return The desired effect.
      */
@@ -411,9 +410,9 @@
      *
      * <p>The waveform will start the first transition from the vibrator off state, with the
      * resonant frequency by default. To provide an initial state, use
-     * {@link #startWaveform(VibrationParameter)}.
+     * {@link #startWaveform(VibrationEffect.VibrationParameter)}.
      *
-     * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+     * @see VibrationEffect.WaveformBuilder
      */
     @NonNull
     public static WaveformBuilder startWaveform() {
@@ -422,14 +421,16 @@
 
     /**
      * Start building a waveform vibration with an initial state specified by a
-     * {@link VibrationParameter}.
+     * {@link VibrationEffect.VibrationParameter}.
      *
      * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
      * control over vibration amplitude and frequency via smooth transitions between values.
      *
-     * @param initialParameter The initial {@link VibrationParameter} value to be applied at the
-     *                         beginning of the vibration.
+     * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be
+     *                         applied at the beginning of the vibration.
      * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+     *
+     * @see VibrationEffect.WaveformBuilder
      */
     @NonNull
     public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) {
@@ -440,17 +441,19 @@
 
     /**
      * Start building a waveform vibration with an initial state specified by two
-     * {@link VibrationParameter VibrationParameters}.
+     * {@link VibrationEffect.VibrationParameter VibrationParameters}.
      *
      * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
      * control over vibration amplitude and frequency via smooth transitions between values.
      *
-     * @param initialParameter1 The initial {@link VibrationParameter} value to be applied at the
-     *                          beginning of the vibration.
-     * @param initialParameter2 The initial {@link VibrationParameter} value to be applied at the
-     *                          beginning of the vibration, must be a different type of parameter
-     *                          than the one specified by the first argument.
+     * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be
+     *                          applied at the beginning of the vibration.
+     * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be
+     *                          applied at the beginning of the vibration, must be a different type
+     *                          of parameter than the one specified by the first argument.
      * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+     *
+     * @see VibrationEffect.WaveformBuilder
      */
     @NonNull
     public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1,
@@ -806,7 +809,46 @@
     }
 
     /**
-     * A composition of haptic primitives that, when combined, create a single haptic effect.
+     * A composition of haptic elements that are combined to be playable as a single
+     * {@link VibrationEffect}.
+     *
+     * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and
+     * can be added to a composition to create a custom vibration effect. Here is an example of an
+     * effect that grows in intensity and then dies off, with a longer rising portion for emphasis
+     * and an extra tick 100ms after:
+     *
+     * <code>
+     * VibrationEffect effect = VibrationEffect.startComposition()
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+     *     .compose();
+     * </code>
+     *
+     * <p>Composition elements can also be {@link VibrationEffect} instances, including other
+     * compositions, and off durations, which are periods of time when the vibrator will be
+     * turned off. Here is an example of a composition that "warms up" with a light tap,
+     * a stronger double tap, then repeats a vibration pattern indefinitely:
+     *
+     * <code>
+     * VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+     *     .addOffDuration(Duration.ofMillis(10))
+     *     .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+     *     .addOffDuration(Duration.ofMillis(50))
+     *     .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex))
+     *     .compose();
+     * </code>
+     *
+     * <p>When choosing to play a composed effect, you should check that individual components are
+     * supported by the device by using the appropriate vibrator method:
+     *
+     * <ul>
+     *     <li>Primitive support can be checked using {@link Vibrator#arePrimitivesSupported}.
+     *     <li>Effect support can be checked using {@link Vibrator#areEffectsSupported}.
+     *     <li>Amplitude control for one-shot and waveforms with amplitude values can be checked
+     *         using {@link Vibrator#hasAmplitudeControl}.
+     * </ul>
      *
      * @see VibrationEffect#startComposition()
      */
@@ -1092,16 +1134,77 @@
      * A builder for waveform haptic effects.
      *
      * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration
-     * parameters. These parameters can be the vibration amplitude or frequency, for example.
+     * parameters. These parameters can be the vibration amplitude, frequency, or both.
+     *
+     * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms
+     * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms:
+     *
+     * <code>
+     * import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+     * import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+     *
+     * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+     *     .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+     *     .addSustain(Duration.ofMillis(200))
+     *     .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+     *     .build();
+     * </code>
+     *
+     * <p>The initial state of the waveform can be set via
+     * {@link VibrationEffect#startWaveform(VibrationParameter)} or
+     * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial
+     * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off,
+     * represented by zero amplitude, at the vibrator's resonant frequency.
+     *
+     * <p>Repeating waveforms can be created by building the repeating block separately and adding
+     * it to the end of a composition with
+     * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}:
      *
      * <p>Note that physical vibration actuators have different reaction times for changing
      * amplitude and frequency. Durations specified here represent a timeline for the target
      * parameters, and quality of effects may be improved if the durations allow time for a
      * transition to be smoothly applied.
      *
-     * <p>Repeating waveforms can be built by constructing the repeating block separately and adding
-     * it to the end of a composition using
-     * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}.
+     * <p>The following example illustrates both an initial state and a repeating section, using
+     * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a
+     * repeated beating effect with a rise that stretches out and a sharp finish.
+     *
+     * <code>
+     * VibrationEffect patternToBeRepeated = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+     *     .addSustain(Duration.ofMillis(10))
+     *     .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+     *     .addSustain(Duration.ofMillis(30))
+     *     .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+     *     .addSustain(Duration.ofMillis(50))
+     *     .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+     *     .build();
+     *
+     * VibrationEffect effect = VibrationEffect.startComposition()
+     *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+     *     .addOffDuration(Duration.ofMillis(20))
+     *     .repeatEffectIndefinitely(patternToBeRepeated)
+     *     .compose();
+     * </code>
+     *
+     * <p>The amplitude step waveforms that can be created via
+     * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with
+     * {@link WaveformBuilder} by adding zero duration transitions:
+     *
+     * <code>
+     * // These two effects are the same
+     * VibrationEffect waveform = VibrationEffect.createWaveform(
+     *     new long[] { 10, 20, 30 },  // timings in milliseconds
+     *     new int[] { 51, 102, 204 }, // amplitudes in [0,255]
+     *     -1);                        // repeat index
+     *
+     * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+     *     .addSustain(Duration.ofMillis(10))
+     *     .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+     *     .addSustain(Duration.ofMillis(20))
+     *     .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+     *     .addSustain(Duration.ofMillis(30))
+     *     .build();
+     * </code>
      *
      * @see VibrationEffect#startWaveform
      */
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 23baa5d..78f1cb1 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -98,7 +98,8 @@
     /**
      * Vibration effect support: unsupported
      *
-     * This effect is <b>not</b> supported by the underlying hardware.
+     * This effect is <b>not</b> natively supported by the underlying hardware, although
+     * the system may still play a fallback vibration.
      */
     public static final int VIBRATION_EFFECT_SUPPORT_NO = 2;
 
@@ -485,20 +486,25 @@
             String reason, @NonNull VibrationAttributes attributes);
 
     /**
-     * Query whether the vibrator supports the given effects.
+     * Query whether the vibrator natively supports the given effects.
      *
-     * Not all hardware reports its effect capabilities, so the system may not necessarily know
-     * whether an effect is supported or not.
+     * <p>If an effect is not supported, the system may still automatically fall back to playing
+     * a simpler vibration instead, which is not optimised for the specific device. This includes
+     * the unknown case, which can't be determined in advance, that will dynamically attempt to
+     * fall back if the optimised effect fails to play.
      *
-     * The returned array will be the same length as the query array and the value at a given index
-     * will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index in the
-     * querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't supported, or
-     * {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether it's
-     * supported or not.
+     * <p>The returned array will be the same length as the query array and the value at a given
+     * index will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index
+     * in the querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't
+     * supported, or {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether
+     * it's supported or not, as some hardware doesn't report its effect capabilities.
+     *
+     * <p>Use {@link #areAllEffectsSupported(int...)} to get a single combined result,
+     * or for convenience when querying exactly one effect.
      *
      * @param effectIds Which effects to query for.
      * @return An array containing the systems current knowledge about whether the given effects
-     * are supported or not.
+     * are natively supported by the device, or not.
      */
     @NonNull
     @VibrationEffectSupport
@@ -515,39 +521,47 @@
     /**
      * Query whether the vibrator supports all of the given effects.
      *
-     * Not all hardware reports its effect capabilities, so the system may not necessarily know
-     * whether an effect is supported or not.
+     * <p>If an effect is not supported, the system may still automatically fall back to a simpler
+     * vibration instead, which is not optimised for the specific device, however vibration isn't
+     * guaranteed in this case.
      *
-     * If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are
+     * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are
      * supported by the hardware.
      *
-     * If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the
-     * query is not supported.
+     * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the
+     * query is not supported, and using them may fall back to an un-optimized vibration or no
+     * vibration.
      *
-     * If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know whether
-     * all of the effects are supported. It may support any or all of the queried effects,
+     * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know
+     * whether all of the effects are supported. It may support any or all of the queried effects,
      * but there's no way to programmatically know whether a {@link #vibrate} call will successfully
      * cause a vibration. It's guaranteed, however, that none of the queried effects are
      * definitively unsupported by the hardware.
      *
+     * <p>Use {@link #areEffectsSupported(int...)} to get individual results for each effect.
+     *
      * @param effectIds Which effects to query for.
-     * @return Whether all of the effects are supported.
+     * @return Whether all of the effects are natively supported by the device.
      */
     @VibrationEffectSupport
     public final int areAllEffectsSupported(
             @NonNull @VibrationEffect.EffectType int... effectIds) {
-        int support = VIBRATION_EFFECT_SUPPORT_YES;
-        for (int supported : areEffectsSupported(effectIds)) {
-            if (supported == VIBRATION_EFFECT_SUPPORT_NO) {
-                return VIBRATION_EFFECT_SUPPORT_NO;
-            } else if (supported == VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
-                support = VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+        VibratorInfo info = getInfo();
+        int allSupported = VIBRATION_EFFECT_SUPPORT_YES;
+        for (int effectId : effectIds) {
+            switch (info.isEffectSupported(effectId)) {
+                case VIBRATION_EFFECT_SUPPORT_NO:
+                    return VIBRATION_EFFECT_SUPPORT_NO;
+                case VIBRATION_EFFECT_SUPPORT_YES:
+                    continue;
+                default: // VIBRATION_EFFECT_SUPPORT_UNKNOWN
+                    allSupported = VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+                    break;
             }
         }
-        return support;
+        return allSupported;
     }
 
-
     /**
      * Query whether the vibrator supports the given primitives.
      *
@@ -555,6 +569,12 @@
      * will contain whether the effect at that same index in the querying array is supported or
      * not.
      *
+     * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if
+     * it is played.
+     *
+     * <p>Use {@link #areAllPrimitivesSupported(int...)} to get a single combined result,
+     * or for convenience when querying exactly one primitive.
+     *
      * @param primitiveIds Which primitives to query for.
      * @return Whether the primitives are supported.
      */
@@ -572,13 +592,19 @@
     /**
      * Query whether the vibrator supports all of the given primitives.
      *
+     * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if
+     * it is played.
+     *
+     * <p>Use {@link #arePrimitivesSupported(int...)} to get individual results for each primitive.
+     *
      * @param primitiveIds Which primitives to query for.
-     * @return Whether primitives effects are supported.
+     * @return Whether all specified primitives are supported.
      */
     public final boolean areAllPrimitivesSupported(
             @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
-        for (boolean supported : arePrimitivesSupported(primitiveIds)) {
-            if (!supported) {
+        VibratorInfo info = getInfo();
+        for (int primitiveId : primitiveIds) {
+            if (!info.isPrimitiveSupported(primitiveId)) {
                 return false;
             }
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 14055ac..ee6f9c0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2436,6 +2436,23 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_MMS_MESSAGE_SETTING = "android.settings.MMS_MESSAGE_SETTING";
 
+    /**
+     * Activity Action: Show a screen of bedtime settings, which is provided by the wellbeing app.
+     * <p>
+     * The handler of this intent action may not exist.
+     * <p>
+     * To start an activity with this intent, apps should set the wellbeing package explicitly in
+     * the intent together with this action. The wellbeing package is defined in
+     * {@code com.android.internal.R.string.config_defaultWellbeingPackage}.
+     * <p>
+     * Output: Nothing
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
+
     // End of Intent actions for Settings
 
     /**
@@ -6077,9 +6094,11 @@
         }
 
         /** @hide */
-        @UnsupportedAppUsage
-        public static String getStringForUser(ContentResolver resolver, String name,
-                int userHandle) {
+        @SystemApi
+        @Nullable
+        @SuppressLint("VisiblySynchronized")
+        public static String getStringForUser(@NonNull ContentResolver resolver,
+                @NonNull String name, int userHandle) {
             if (MOVED_TO_GLOBAL.contains(name)) {
                 Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
                         + " to android.provider.Settings.Global.");
@@ -6311,8 +6330,9 @@
         }
 
         /** @hide */
-        @UnsupportedAppUsage
-        public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+        @SystemApi
+        public static int getIntForUser(@NonNull ContentResolver cr, @NonNull String name,
+                int def, int userHandle) {
             String v = getStringForUser(cr, name, userHandle);
             return parseIntSettingWithDefault(v, def);
         }
@@ -10048,6 +10068,13 @@
         public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
 
         /**
+         * The duration of timeout, in milliseconds, to switch from a non-primary user to the
+         * primary user when the device is docked.
+         * @hide
+         */
+        public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+
+        /**
          * Backup manager behavioral parameters.
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -10281,6 +10308,15 @@
         public static final String NEARBY_SHARING_SLICE_URI = "nearby_sharing_slice_uri";
 
         /**
+         * Current provider of Fast Pair saved devices page.
+         * Default value in @string/config_defaultNearbyFastPairSettingsDevicesComponent.
+         * No VALIDATOR as this setting will not be backed up.
+         * @hide
+         */
+        public static final String NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT =
+                "nearby_fast_pair_settings_devices_component";
+
+        /**
          * Controls whether aware is enabled.
          * @hide
          */
@@ -10632,6 +10668,15 @@
         public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
 
         /**
+         * Setting to store denylisted system languages by the CEC {@code <Set Menu Language>}
+         * confirmation dialog.
+         *
+         * @hide
+         */
+        public static final String HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST =
+                "hdmi_cec_set_menu_language_denylist";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java
index 49ab5db..f5c59b5 100644
--- a/core/java/android/service/attention/AttentionService.java
+++ b/core/java/android/service/attention/AttentionService.java
@@ -24,11 +24,14 @@
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Slog;
 
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
 
 
 /**
@@ -51,6 +54,7 @@
  */
 @SystemApi
 public abstract class AttentionService extends Service {
+    private static final String LOG_TAG = "AttentionService";
     /**
      * The {@link Intent} that must be declared as handled by the service. To be supported, the
      * service must also require the {@link android.Manifest.permission#BIND_ATTENTION_SERVICE}
@@ -80,6 +84,9 @@
     /** Camera permission is not granted. */
     public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6;
 
+    /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */
+    public static final double PROXIMITY_UNKNOWN = -1;
+
     /**
      * Result codes for when attention check was successful.
      *
@@ -118,6 +125,20 @@
             Preconditions.checkNotNull(callback);
             AttentionService.this.onCancelAttentionCheck(new AttentionCallback(callback));
         }
+
+        /** {@inheritDoc} */
+        @Override
+        public void onStartProximityUpdates(IProximityCallback callback) {
+            Objects.requireNonNull(callback);
+            AttentionService.this.onStartProximityUpdates(new ProximityCallback(callback));
+
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void onStopProximityUpdates() {
+            AttentionService.this.onStopProximityUpdates();
+        }
     };
 
     @Nullable
@@ -143,6 +164,23 @@
      */
     public abstract void onCancelAttentionCheck(@NonNull AttentionCallback callback);
 
+    /**
+     * Requests the continuous updates of proximity signal via the provided callback,
+     * until the given callback is unregistered.
+     *
+     * @param callback the callback to return the result to
+     */
+    public void onStartProximityUpdates(@NonNull ProximityCallback callback) {
+        Slog.w(LOG_TAG, "Override this method.");
+    }
+
+    /**
+     * Requests to stop providing continuous updates until the callback is registered.
+     */
+    public void onStopProximityUpdates() {
+        Slog.w(LOG_TAG, "Override this method.");
+    }
+
     /** Callbacks for AttentionService results. */
     public static final class AttentionCallback {
         @NonNull private final IAttentionCallback mCallback;
@@ -174,4 +212,26 @@
             }
         }
     }
+
+    /** Callbacks for ProximityCallback results. */
+    public static final class ProximityCallback {
+        @NonNull private final WeakReference<IProximityCallback> mCallback;
+
+        private ProximityCallback(@NonNull IProximityCallback callback) {
+            mCallback = new WeakReference<>(callback);
+        }
+
+        /**
+         * @param distance the estimated distance of the user (in meter)
+         * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
+         *
+         */
+        public void onProximityUpdate(double distance) {
+            try {
+                mCallback.get().onProximityUpdate(distance);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
 }
diff --git a/core/java/android/service/attention/IAttentionService.aidl b/core/java/android/service/attention/IAttentionService.aidl
index 99e7997..8bb881b 100644
--- a/core/java/android/service/attention/IAttentionService.aidl
+++ b/core/java/android/service/attention/IAttentionService.aidl
@@ -17,6 +17,7 @@
 package android.service.attention;
 
 import android.service.attention.IAttentionCallback;
+import android.service.attention.IProximityCallback;
 
 /**
  * Interface for a concrete implementation to provide to the AttentionManagerService.
@@ -26,4 +27,6 @@
 oneway interface IAttentionService {
     void checkAttention(IAttentionCallback callback);
     void cancelAttentionCheck(IAttentionCallback callback);
+    void onStartProximityUpdates(IProximityCallback callback);
+    void onStopProximityUpdates();
 }
\ No newline at end of file
diff --git a/core/java/android/service/attention/IProximityCallback.aidl b/core/java/android/service/attention/IProximityCallback.aidl
new file mode 100644
index 0000000..9ecf9bc
--- /dev/null
+++ b/core/java/android/service/attention/IProximityCallback.aidl
@@ -0,0 +1,10 @@
+package android.service.attention;
+
+/**
+ * Callback for onStartProximityUpdates request.
+ *
+ * @hide
+ */
+oneway interface IProximityCallback {
+    void onProximityUpdate(double distance);
+}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 29c7796..cb1b5d3 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -578,6 +578,14 @@
     public static final String SERVICE_META_DATA = "android.autofill";
 
     /**
+     * Name of the {@link FillResponse} extra used to return a delayed fill response.
+     *
+     * <p>Please see {@link FillRequest#getDelayedFillIntentSender()} on how to send a delayed
+     * fill response to framework.</p>
+     */
+    public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
+
+    /**
      * Name of the {@link IResultReceiver} extra used to return the primary result of a request.
      *
      * @hide
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index f820f03..e4d3732 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -160,6 +161,19 @@
      */
     private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
 
+    /**
+     * Gets the {@link IntentSender} to send a delayed fill response.
+     *
+     * <p>The autofill service must first indicate that it wants to return a delayed
+     * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+     * fill response. Then it can use this IntentSender to send an Intent with extra
+     * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+     *
+     * <p>Note that this may be null if a delayed fill response is not supported for
+     * this fill request.</p>
+     */
+    private final @Nullable IntentSender mDelayedFillIntentSender;
+
     private void onConstructed() {
         Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
     }
@@ -252,6 +266,16 @@
      *
      *   <p>The Autofill Service must set supportsInlineSuggestions in its XML to enable support
      *   for inline suggestions.</p>
+     * @param delayedFillIntentSender
+     *   Gets the {@link IntentSender} to send a delayed fill response.
+     *
+     *   <p>The autofill service must first indicate that it wants to return a delayed
+     *   {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+     *   fill response. Then it can use this IntentSender to send an Intent with extra
+     *   {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+     *
+     *   <p>Note that this may be null if a delayed fill response is not supported for
+     *   this fill request.</p>
      * @hide
      */
     @DataClass.Generated.Member
@@ -260,7 +284,8 @@
             @NonNull List<FillContext> fillContexts,
             @Nullable Bundle clientState,
             @RequestFlags int flags,
-            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
+            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
+            @Nullable IntentSender delayedFillIntentSender) {
         this.mId = id;
         this.mFillContexts = fillContexts;
         com.android.internal.util.AnnotationValidations.validate(
@@ -276,6 +301,7 @@
                         | FLAG_VIEW_NOT_FOCUSED
                         | FLAG_ACTIVITY_START);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+        this.mDelayedFillIntentSender = delayedFillIntentSender;
 
         onConstructed();
     }
@@ -348,6 +374,22 @@
         return mInlineSuggestionsRequest;
     }
 
+    /**
+     * Gets the {@link IntentSender} to send a delayed fill response.
+     *
+     * <p>The autofill service must first indicate that it wants to return a delayed
+     * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+     * fill response. Then it can use this IntentSender to send an Intent with extra
+     * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+     *
+     * <p>Note that this may be null if a delayed fill response is not supported for
+     * this fill request.</p>
+     */
+    @DataClass.Generated.Member
+    public @Nullable IntentSender getDelayedFillIntentSender() {
+        return mDelayedFillIntentSender;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -359,7 +401,8 @@
                 "fillContexts = " + mFillContexts + ", " +
                 "clientState = " + mClientState + ", " +
                 "flags = " + requestFlagsToString(mFlags) + ", " +
-                "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
+                "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + ", " +
+                "delayedFillIntentSender = " + mDelayedFillIntentSender +
         " }";
     }
 
@@ -372,12 +415,14 @@
         byte flg = 0;
         if (mClientState != null) flg |= 0x4;
         if (mInlineSuggestionsRequest != null) flg |= 0x10;
+        if (mDelayedFillIntentSender != null) flg |= 0x20;
         dest.writeByte(flg);
         dest.writeInt(mId);
         dest.writeParcelableList(mFillContexts, flags);
         if (mClientState != null) dest.writeBundle(mClientState);
         dest.writeInt(mFlags);
         if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
+        if (mDelayedFillIntentSender != null) dest.writeTypedObject(mDelayedFillIntentSender, flags);
     }
 
     @Override
@@ -398,6 +443,7 @@
         Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
         int flags = in.readInt();
         InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+        IntentSender delayedFillIntentSender = (flg & 0x20) == 0 ? null : (IntentSender) in.readTypedObject(IntentSender.CREATOR);
 
         this.mId = id;
         this.mFillContexts = fillContexts;
@@ -414,6 +460,7 @@
                         | FLAG_VIEW_NOT_FOCUSED
                         | FLAG_ACTIVITY_START);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+        this.mDelayedFillIntentSender = delayedFillIntentSender;
 
         onConstructed();
     }
@@ -433,10 +480,10 @@
     };
 
     @DataClass.Generated(
-            time = 1643052544776L,
+            time = 1643386870464L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
-            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 903e77f..296877a 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -65,10 +65,22 @@
      */
     public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
 
+    /**
+     * Flag used to request to wait for a delayed fill from the remote Autofill service if it's
+     * passed to {@link Builder#setFlags(int)}.
+     *
+     * <p>Some datasets (i.e. OTP) take time to produce. This flags allows remote service to send
+     * a {@link FillResponse} to the latest {@link FillRequest} via
+     * {@link FillRequest#getDelayedFillIntentSender()} even if the original {@link FillCallback}
+     * has timed out.
+     */
+    public static final int FLAG_DELAY_FILL = 0x4;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
             FLAG_TRACK_CONTEXT_COMMITED,
-            FLAG_DISABLE_ACTIVITY_ONLY
+            FLAG_DISABLE_ACTIVITY_ONLY,
+            FLAG_DELAY_FILL
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface FillResponseFlags {}
@@ -657,7 +669,7 @@
         public Builder setFlags(@FillResponseFlags int flags) {
             throwIfDestroyed();
             mFlags = Preconditions.checkFlagsArgument(flags,
-                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL);
             return this;
         }
 
diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java
index 870a7e3..9df8358 100644
--- a/core/java/android/service/games/GameService.java
+++ b/core/java/android/service/games/GameService.java
@@ -16,9 +16,11 @@
 
 package android.service.games;
 
+import android.Manifest;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.IGameManagerService;
@@ -173,6 +175,7 @@
      *
      * @param taskId The taskId of the game.
      */
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
     public final void createGameSession(@IntRange(from = 0) int taskId) {
         if (mGameServiceController == null) {
             throw new IllegalStateException("Can not call before connected()");
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index f4baedc..e33f180 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -84,6 +84,15 @@
         }
 
         @Override
+        public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+                boolean visibleDueToGesture) {
+            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+                    GameSession::dispatchTransientSystemBarVisibilityFromRevealGestureChanged,
+                    GameSession.this,
+                    visibleDueToGesture));
+        }
+
+        @Override
         public void onTaskFocusChanged(boolean focused) {
             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
                     GameSession::moveToState, GameSession.this,
@@ -109,6 +118,7 @@
     }
 
     private LifecycleState mLifecycleState = LifecycleState.INITIALIZED;
+    private boolean mAreTransientInsetsVisibleDueToGesture = false;
     private IGameSessionController mGameSessionController;
     private int mTaskId;
     private GameSessionRootView mGameSessionRootView;
@@ -138,11 +148,23 @@
     }
 
     @Hide
-    void doDestroy() {
+    private void doDestroy() {
         mSurfaceControlViewHost.release();
         moveToState(LifecycleState.DESTROYED);
     }
 
+    /** @hide */
+    @VisibleForTesting
+    @MainThread
+    public void dispatchTransientSystemBarVisibilityFromRevealGestureChanged(
+            boolean visibleDueToGesture) {
+        boolean didValueChange = mAreTransientInsetsVisibleDueToGesture != visibleDueToGesture;
+        mAreTransientInsetsVisibleDueToGesture = visibleDueToGesture;
+        if (didValueChange) {
+            onTransientSystemBarVisibilityFromRevealGestureChanged(visibleDueToGesture);
+        }
+    }
+
     /**
      * @hide
      */
@@ -252,7 +274,23 @@
      *
      * @param focused True if the game task is focused, false if the game task is unfocused.
      */
-    public void onGameTaskFocusChanged(boolean focused) {}
+    public void onGameTaskFocusChanged(boolean focused) {
+    }
+
+    /**
+     * Called when the visibility of the transient system bars changed due to the user performing
+     * the reveal gesture. The reveal gesture is defined as a swipe to reveal the transient system
+     * bars that originates from the system bars.
+     *
+     * @param visibleDueToGesture if the transient bars triggered by the reveal gesture are visible.
+     *                            This is {@code true} when the transient system bars become visible
+     *                            due to user performing the reveal gesture. This is {@code false}
+     *                            when the transient system bars are hidden or become permanently
+     *                            visible.
+     */
+    public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+            boolean visibleDueToGesture) {
+    }
 
     /**
      * Sets the task overlay content to an explicit view. This view is placed directly into the game
@@ -278,7 +316,7 @@
      *
      * @return {@code true} if the game was successfully restarted; otherwise, {@code false}.
      */
-    @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+    @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)
     public final boolean restartGame() {
         try {
             mGameSessionController.restartGame(mTaskId);
@@ -344,12 +382,14 @@
 
         /**
          * Called when taking the screenshot failed.
+         *
          * @param statusCode Indicates the reason for failure.
          */
         void onFailure(@ScreenshotFailureStatus int statusCode);
 
         /**
          * Called when taking the screenshot succeeded.
+         *
          * @param bitmap The screenshot.
          */
         void onSuccess(@NonNull Bitmap bitmap);
diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl
index 71da630..49c36c6 100644
--- a/core/java/android/service/games/IGameSession.aidl
+++ b/core/java/android/service/games/IGameSession.aidl
@@ -21,5 +21,6 @@
  */
 oneway interface IGameSession {
     void onDestroyed();
+    void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean visibleDueToGesture);
     void onTaskFocusChanged(boolean focused);
 }
diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
index 220e498..6b11e74 100644
--- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
+++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
@@ -26,6 +26,7 @@
 oneway interface ITrustAgentServiceCallback {
     void grantTrust(CharSequence message, long durationMs, int flags);
     void revokeTrust();
+    void lockUser();
     void setManagingTrust(boolean managingTrust);
     void onConfigureCompleted(boolean result, IBinder token);
     void addEscrowToken(in byte[] token, int userId);
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index fba61cf..8f6e1e0 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -119,16 +119,15 @@
      * automatically remove trust after some conditions are met (detailed below) with the option for
      * the agent to renew the trust again later.
      *
-     * <p>After this is called, the agent will grant trust until the platform thinks an active user
-     * is no longer using that trust. For example, if the user dismisses keyguard, the platform will
-     * remove trust (this does not automatically lock the device).
+     * <p>After this is called, the agent will grant trust until the platform thinks an active
+     * user is no longer using that trust. This can happen for any reason as determined by the
+     * platform. For example, if the user dismisses keyguard, the platform will remove trust;
+     * since this does not automatically lock the device, this results in the device locking the
+     * next time the screen turns off.
      *
      * <p>When the platform internally removes the agent's trust in this manner, an agent can
      * re-grant it (via a call to grantTrust) without the user having to unlock the device through
      * another method (e.g. PIN). This renewable state only persists for a limited time.
-     *
-     * TODO(b/213631675): Remove @hide
-     * @hide
      */
     public static final int FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE = 1 << 2;
 
@@ -139,9 +138,6 @@
      * Without this flag, the message passed to {@code grantTrust} is only used for debugging
      * purposes. With the flag, it may be displayed to the user as the reason why the device is
      * unlocked.
-     *
-     * TODO(b/213911325): Remove @hide
-     * @hide
      */
     public static final int FLAG_GRANT_TRUST_DISPLAY_MESSAGE = 1 << 3;
 
@@ -309,9 +305,6 @@
      * {@link #grantTrust(CharSequence, long, int)}.
      *
      * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
-     *
-     * TODO(b/213631672): Add CTS tests
-     * @hide
      */
     public void onUserRequestedUnlock() {
     }
@@ -624,11 +617,15 @@
      *
      * If the user has no auth method specified, then keyguard will still be shown but can be
      * dismissed normally.
-     *
-     * TODO(b/213631675): Implement & make public
-     * @hide
      */
     public final void lockUser() {
+        if (mCallback != null) {
+            try {
+                mCallback.lockUser();
+            } catch (RemoteException e) {
+                onError("calling lockUser");
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f2a0355..c91851a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -905,11 +905,12 @@
             if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
                 return;
             }
+
+            SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
             // TODO: apply the dimming to preview as well once surface transparency works in
             // preview mode.
             if (!isPreview() && mShouldDim) {
                 Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
-                SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction();
 
                 // Animate dimming to gradually change the wallpaper alpha from the previous
                 // dim amount to the new amount only if the dim amount changed.
@@ -919,16 +920,15 @@
                         ? 0 : DIMMING_ANIMATION_DURATION_MS);
                 animator.addUpdateListener((ValueAnimator va) -> {
                     final float dimValue = (float) va.getAnimatedValue();
-                    surfaceControl
-                            .setAlpha(mBbqSurfaceControl, 1 - dimValue)
-                            .apply();
+                    if (mBbqSurfaceControl != null) {
+                        surfaceControlTransaction
+                                .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
+                    }
                 });
                 animator.start();
             } else {
                 Log.v(TAG, "Setting wallpaper dimming: " + 0);
-                new SurfaceControl.Transaction()
-                        .setAlpha(mBbqSurfaceControl, 1.0f)
-                        .apply();
+                surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
             }
         }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
index 861a4ed..ca75d2e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
@@ -14,6 +15,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.mediattt;
+package android.service.wallpapereffectsgeneration;
 
-parcelable DeviceInfo;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+/**
+ * Interface from the system to WallpaperEffectsGeneration service.
+ *
+ * @hide
+ */
+oneway interface IWallpaperEffectsGenerationService {
+  void onGenerateCinematicEffect(in CinematicEffectRequest request);
+}
\ No newline at end of file
diff --git a/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java b/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java
new file mode 100644
index 0000000..18b654e
--- /dev/null
+++ b/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.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 android.service.wallpapereffectsgeneration;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * A service for handling wallpaper effects generation tasks. It must implement
+ * (onGenerateCinematicEffect} method to generate response and call returnCinematicEffectResponse
+ * to send the response.
+ *
+ * <p>To extend this service, you must declare the service in your manifest file with the
+ * {@link android.Manifest.permission#BIND_WALLPAPER_EFFECTS_GENERATION} permission and includes
+ * an intent filter with the {@link #SERVICE_INTERFACE} action. For example: </p>
+ * <pre>
+ *     <application>
+ *         <service android:name=".CtsWallpaperEffectsGenerationService"
+ *             android:exported="true"
+ *             android:label="CtsWallpaperEffectsGenerationService"
+ *             android:permission="android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE">
+ *             <intent-filter>
+ *                 <action android:name="android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService"
+ />
+ *             </intent-filter>
+ *         </service>
+ *         <uses-library android:name="android.test.runner"/>
+ *     </application>
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class WallpaperEffectsGenerationService extends Service {
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     *
+     * <p>The service must also require the
+     * {@link android.permission#MANAGE_WALLPAPER_EFFECTS_GENERATION}
+     * permission.
+     *
+     * @hide
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService";
+    private static final boolean DEBUG = false;
+    private static final String TAG = "WallpaperEffectsGenerationService";
+    private Handler mHandler;
+    private IWallpaperEffectsGenerationManager mService;
+
+    private final IWallpaperEffectsGenerationService  mInterface =
+            new IWallpaperEffectsGenerationService.Stub() {
+                @Override
+                public void onGenerateCinematicEffect(CinematicEffectRequest request) {
+                    mHandler.sendMessage(
+                            obtainMessage(
+                                    WallpaperEffectsGenerationService::onGenerateCinematicEffect,
+                                    WallpaperEffectsGenerationService.this, request));
+                }
+            };
+
+    /**
+     * Called when the OS receives a request for generating cinematic effect. On receiving the
+     * request, it extract cinematic information from the input and call
+     * {@link #returnCinematicEffectResponse} with the textured mesh
+     * and metadata wrapped in CinematicEffectResponse.
+     *
+     * @param request the cinematic effect request passed from the client.
+     */
+    public abstract void onGenerateCinematicEffect(@NonNull CinematicEffectRequest request);
+
+    /**
+     * Returns the cinematic effect response. Must be called when cinematic effect
+     * response is generated and ready to be sent back. Otherwise the response won't be
+     * returned.
+     *
+     * @param response the cinematic effect response returned from service provider.
+     */
+    public final void returnCinematicEffectResponse(@NonNull CinematicEffectResponse response) {
+        try {
+            mService.returnCinematicEffectResponse(response);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (DEBUG) {
+            Log.d(TAG, "onCreate WallpaperEffectsGenerationService");
+        }
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+        IBinder b = ServiceManager.getService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE);
+        mService = IWallpaperEffectsGenerationManager.Stub.asInterface(b);
+    }
+
+    @NonNull
+    @Override
+    public final IBinder onBind(@NonNull Intent intent) {
+        if (DEBUG) {
+            Log.d(TAG, "onBind WallpaperEffectsGenerationService");
+        }
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Slog.w(TAG,
+                "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+}
diff --git a/core/java/android/util/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java
index b5e8dd7..5bb263a 100644
--- a/core/java/android/util/IconDrawableFactory.java
+++ b/core/java/android/util/IconDrawableFactory.java
@@ -15,7 +15,12 @@
  */
 package android.util;
 
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE;
+
 import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -37,6 +42,7 @@
     protected final Context mContext;
     protected final PackageManager mPm;
     protected final UserManager mUm;
+    protected final DevicePolicyManager mDpm;
     protected final LauncherIcons mLauncherIcons;
     protected final boolean mEmbedShadow;
 
@@ -44,6 +50,7 @@
         mContext = context;
         mPm = context.getPackageManager();
         mUm = context.getSystemService(UserManager.class);
+        mDpm = context.getSystemService(DevicePolicyManager.class);
         mLauncherIcons = new LauncherIcons(context);
         mEmbedShadow = embedShadow;
     }
@@ -73,18 +80,32 @@
         if (appInfo.isInstantApp()) {
             int badgeColor = Resources.getSystem().getColor(
                     com.android.internal.R.color.instant_app_badge, null);
+            Drawable badge = mContext.getDrawable(
+                    com.android.internal.R.drawable.ic_instant_icon_badge_bolt);
             icon = mLauncherIcons.getBadgedDrawable(icon,
-                    com.android.internal.R.drawable.ic_instant_icon_badge_bolt,
+                    badge,
                     badgeColor);
         }
         if (mUm.hasBadge(userId)) {
-            icon = mLauncherIcons.getBadgedDrawable(icon,
-                    mUm.getUserIconBadgeResId(userId),
-                    mUm.getUserBadgeColor(userId));
+
+            Drawable badge = mDpm.getDrawable(
+                    getUpdatableUserIconBadgeId(userId),
+                    SOLID_COLORED,
+                    () -> getDefaultUserIconBadge(userId));
+
+            icon = mLauncherIcons.getBadgedDrawable(icon, badge, mUm.getUserBadgeColor(userId));
         }
         return icon;
     }
 
+    private String getUpdatableUserIconBadgeId(int userId) {
+        return mUm.isManagedProfile(userId) ? WORK_PROFILE_ICON_BADGE : UNDEFINED;
+    }
+
+    private Drawable getDefaultUserIconBadge(int userId) {
+        return mContext.getResources().getDrawable(mUm.getUserIconBadgeResId(userId));
+    }
+
     /**
      * Add shadow to the icon if {@link AdaptiveIconDrawable}
      */
diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java
index e652e17..355b2e9 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -45,10 +45,12 @@
     private final SparseArray<Bitmap> mShadowCache = new SparseArray<>();
     private final int mIconSize;
     private final Resources mRes;
+    private final Context mContext;
 
     public LauncherIcons(Context context) {
         mRes = context.getResources();
         mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
+        mContext = context;
     }
 
     public Drawable wrapIconDrawableWithShadow(Drawable drawable) {
@@ -98,14 +100,14 @@
         return shadow;
     }
 
-    public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) {
-        return getBadgedDrawable(null, foregroundRes, backgroundColor);
+    public Drawable getBadgeDrawable(Drawable badgeForeground, int backgroundColor) {
+        return getBadgedDrawable(null, badgeForeground, backgroundColor);
     }
 
-    public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) {
+    public Drawable getBadgedDrawable(
+            Drawable base, Drawable badgeForeground, int backgroundColor) {
         Resources overlayableRes =
                 ActivityThread.currentActivityThread().getApplication().getResources();
-
         // ic_corp_icon_badge_shadow is not work-profile-specific.
         Drawable badgeShadow = overlayableRes.getDrawable(
                 com.android.internal.R.drawable.ic_corp_icon_badge_shadow);
@@ -115,7 +117,6 @@
                 com.android.internal.R.drawable.ic_corp_icon_badge_color)
                 .getConstantState().newDrawable().mutate();
 
-        Drawable badgeForeground = overlayableRes.getDrawable(foregroundRes);
         badgeForeground.setTint(backgroundColor);
 
         Drawable[] drawables = base == null
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index fa39380..246a8c9 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1118,6 +1118,19 @@
     }
 
     /**
+     * Returns the system's preferred display mode. This mode will be used when the user has not
+     * specified a display-mode preference. This returns null if the boot display mode feature is
+     * not supported by system.
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public Display.Mode getSystemPreferredDisplayMode() {
+        return mGlobal.getSystemPreferredDisplayMode(getDisplayId());
+    }
+
+    /**
      * Returns the display's HDR capabilities.
      *
      * @see #isHdr()
diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index 449e9b3..67ae743 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -63,5 +63,5 @@
     /**
      * Called when the keep clear ares on a display have changed.
      */
-    void onKeepClearAreasChanged(int displayId, in List<Rect> keepClearAreas);
+    void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted);
 }
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index ad1f201..8801fe0 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -79,13 +79,17 @@
 
 
 
-    // Code below generated by codegen v1.0.7.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/InputMonitor.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
 
 
     @DataClass.Generated.Member
@@ -126,7 +130,7 @@
 
     @Override
     @DataClass.Generated.Member
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
@@ -141,7 +145,7 @@
     /** @hide */
     @SuppressWarnings({"unchecked", "RedundantCast"})
     @DataClass.Generated.Member
-    /* package-private */ InputMonitor(Parcel in) {
+    /* package-private */ InputMonitor(@NonNull Parcel in) {
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
@@ -167,17 +171,21 @@
         }
 
         @Override
-        public InputMonitor createFromParcel(Parcel in) {
+        public InputMonitor createFromParcel(@NonNull Parcel in) {
             return new InputMonitor(in);
         }
     };
 
     @DataClass.Generated(
-            time = 1571177265149L,
-            codegenVersion = "1.0.7",
+            time = 1637697281750L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
             inputSignatures = "private static final  java.lang.String TAG\nprivate static final  boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic  void pilferPointers()\npublic  void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
     @Deprecated
     private void __metadata() {}
 
+
+    //@formatter:on
+    // End of generated code
+
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 904d7c8..6f5fea2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -197,6 +197,9 @@
     private static native int[] nativeGetCompositionDataspaces();
     private static native boolean nativeSetActiveColorMode(IBinder displayToken,
             int colorMode);
+    private static native boolean nativeGetBootDisplayModeSupport();
+    private static native void nativeSetBootDisplayMode(IBinder displayToken, int displayMode);
+    private static native void nativeClearBootDisplayMode(IBinder displayToken);
     private static native void nativeSetAutoLowLatencyMode(IBinder displayToken, boolean on);
     private static native void nativeSetGameContentType(IBinder displayToken, boolean on);
     private static native void nativeSetDisplayPowerMode(
@@ -1878,6 +1881,8 @@
         public boolean autoLowLatencyModeSupported;
         public boolean gameContentTypeSupported;
 
+        public int preferredBootDisplayMode;
+
         @Override
         public String toString() {
             return "DynamicDisplayInfo{"
@@ -1887,7 +1892,8 @@
                     + ", activeColorMode=" + activeColorMode
                     + ", hdrCapabilities=" + hdrCapabilities
                     + ", autoLowLatencyModeSupported=" + autoLowLatencyModeSupported
-                    + ", gameContentTypeSupported" + gameContentTypeSupported + "}";
+                    + ", gameContentTypeSupported" + gameContentTypeSupported
+                    + ", preferredBootDisplayMode" + preferredBootDisplayMode + "}";
         }
 
         @Override
@@ -1899,7 +1905,8 @@
                 && activeDisplayModeId == that.activeDisplayModeId
                 && Arrays.equals(supportedColorModes, that.supportedColorModes)
                 && activeColorMode == that.activeColorMode
-                && Objects.equals(hdrCapabilities, that.hdrCapabilities);
+                && Objects.equals(hdrCapabilities, that.hdrCapabilities)
+                && preferredBootDisplayMode == that.preferredBootDisplayMode;
         }
 
         @Override
@@ -2266,6 +2273,36 @@
     /**
      * @hide
      */
+    public static boolean getBootDisplayModeSupport() {
+        return nativeGetBootDisplayModeSupport();
+    }
+
+    /** There is no associated getter for this method.  When this is set, the display is expected
+     * to start up in this mode next time the device reboots.
+     * @hide
+     */
+    public static void setBootDisplayMode(IBinder displayToken, int displayModeId) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        nativeSetBootDisplayMode(displayToken, displayModeId);
+    }
+
+    /**
+     * @hide
+     */
+    public static void clearBootDisplayMode(IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        nativeClearBootDisplayMode(displayToken);
+    }
+
+    /**
+     * @hide
+     */
     public static void setAutoLowLatencyMode(IBinder displayToken, boolean on) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 039b50a..8401b7c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -295,6 +295,9 @@
     private OnWindowDismissedCallback mOnWindowDismissedCallback;
     private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback;
     private WindowControllerCallback mWindowControllerCallback;
+    @WindowInsetsController.Appearance
+    private int mSystemBarAppearance;
+    private DecorCallback mDecorCallback;
     private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener;
     private Rect mRestrictedCaptionAreaRect;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -607,17 +610,6 @@
          * @param hasCapture True if the window has pointer capture.
          */
         default public void onPointerCaptureChanged(boolean hasCapture) { };
-
-        /**
-         * Called from
-         * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
-         *
-         * @param appearance The newly applied appearance.
-         * @hide
-         */
-        default void onSystemBarAppearanceChanged(
-                @WindowInsetsController.Appearance int appearance) {
-        }
     }
 
     /** @hide */
@@ -672,6 +664,35 @@
         void updateNavigationBarColor(int color);
     }
 
+    /** @hide */
+    public interface DecorCallback {
+        /**
+         * Called from
+         * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
+         *
+         * @param appearance The newly applied appearance.
+         */
+        void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance);
+
+        /**
+         * Called from
+         * {@link com.android.internal.policy.DecorView#updateColorViews(WindowInsets, boolean)}
+         * when {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground} is
+         * being updated.
+         *
+         * @param drawLegacyNavigationBarBackground the new value that is being set to
+         *        {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground}.
+         * @return The value to be set to
+         *   {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackgroundHandled}
+         *         on behalf of the {@link com.android.internal.policy.DecorView}.
+         *         {@code true} to tell that the Window can render the legacy navigation bar
+         *         background on behalf of the {@link com.android.internal.policy.DecorView}.
+         *         {@code false} to let {@link com.android.internal.policy.DecorView} handle it.
+         */
+        boolean onDrawLegacyNavigationBarBackgroundChanged(
+                boolean drawLegacyNavigationBarBackground);
+    }
+
     /**
      * Callback for clients that want to be aware of where caption draws content.
      */
@@ -996,6 +1017,36 @@
         return mWindowControllerCallback;
     }
 
+    /** @hide */
+    public final void setDecorCallback(DecorCallback decorCallback) {
+        mDecorCallback = decorCallback;
+    }
+
+    /** @hide */
+    @WindowInsetsController.Appearance
+    public final int getSystemBarAppearance() {
+        return mSystemBarAppearance;
+    }
+
+    /** @hide */
+    public final void dispatchOnSystemBarAppearanceChanged(
+            @WindowInsetsController.Appearance int appearance) {
+        mSystemBarAppearance = appearance;
+        if (mDecorCallback != null) {
+            mDecorCallback.onSystemBarAppearanceChanged(appearance);
+        }
+    }
+
+    /** @hide */
+    public final boolean onDrawLegacyNavigationBarBackgroundChanged(
+            boolean drawLegacyNavigationBarBackground) {
+        if (mDecorCallback == null) {
+            return false;
+        }
+        return mDecorCallback.onDrawLegacyNavigationBarBackgroundChanged(
+                drawLegacyNavigationBarBackground);
+    }
+
     /**
      * Set a callback for changes of area where caption will draw its content.
      *
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 115e9e8..02c8945 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -163,10 +163,5 @@
     public void onPointerCaptureChanged(boolean hasCapture) {
         mWrapped.onPointerCaptureChanged(hasCapture);
     }
-
-    @Override
-    public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
-        mWrapped.onSystemBarAppearanceChanged(appearance);
-    }
 }
 
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 0a33d6c..a31cacf 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -710,6 +710,8 @@
 
     private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 0x0400000;
 
+    private static final int BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE = 0x0800000;
+
     /**
      * Bits that provide the id of a virtual descendant of a view.
      */
@@ -2276,6 +2278,38 @@
     }
 
     /**
+     * Gets if the node has selectable text.
+     *
+     * <p>
+     *     Services should use {@link #ACTION_SET_SELECTION} for selection. Editable text nodes must
+     *     also be selectable. But not all UIs will populate this field, so services should consider
+     *     'isTextSelectable | isEditable' to ensure they don't miss nodes with selectable text.
+     * </p>
+     *
+     * @see #isEditable
+     * @return True if the node has selectable text.
+     */
+    public boolean isTextSelectable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE);
+    }
+
+    /**
+     * Sets if the node has selectable text.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param selectableText True if the node has selectable text, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setTextSelectable(boolean selectableText) {
+        setBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE, selectableText);
+    }
+
+    /**
      * Gets if the node is editable.
      *
      * @return True if the node is editable, false otherwise.
@@ -4327,8 +4361,12 @@
                 return "ACTION_CANCEL_DRAG";
             case R.id.accessibilityActionDragDrop:
                 return "ACTION_DROP";
-            default:
+            default: {
+                if (action == R.id.accessibilityActionShowSuggestions) {
+                    return "ACTION_SHOW_SUGGESTIONS";
+                }
                 return "ACTION_UNKNOWN";
+            }
         }
     }
 
@@ -4462,6 +4500,7 @@
         builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
         builder.append("; visible: ").append(isVisibleToUser());
         builder.append("; actions: ").append(mActions);
+        builder.append("; isTextSelectable: ").append(isTextSelectable());
 
         return builder.toString();
     }
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index ab749ee..3914a3c 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -865,6 +865,15 @@
     }
 
     /**
+     * @return if a window animation has outsets applied to it.
+     *
+     * @hide
+     */
+    public boolean hasExtension() {
+        return false;
+    }
+
+    /**
      * If showBackground is {@code true} and this animation is applied on a window, then the windows
      * in the animation will animate with the background associated with this window behind them.
      *
@@ -942,6 +951,21 @@
     }
 
     /**
+     * Gets the transformation to apply a specific point in time. Implementations of this method
+     * should always be kept in sync with getTransformation.
+     *
+     * @param normalizedTime time between 0 and 1 where 0 is the start of the animation and 1 the
+     *                       end.
+     * @param outTransformation A transformation object that is provided by the
+     *        caller and will be filled in by the animation.
+     * @hide
+     */
+    public void getTransformationAt(float normalizedTime, Transformation outTransformation) {
+        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
+        applyTransformation(interpolatedTime, outTransformation);
+    }
+
+    /**
      * Gets the transformation to apply at a specified point in time. Implementations of this
      * method should always replace the specified Transformation or document they are doing
      * otherwise.
@@ -987,8 +1011,7 @@
                 normalizedTime = 1.0f - normalizedTime;
             }
 
-            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
-            applyTransformation(interpolatedTime, outTransformation);
+            getTransformationAt(normalizedTime, outTransformation);
         }
 
         if (expired) {
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 03c6ca6..a2f3544 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -363,6 +363,26 @@
      * The transformation of an animation set is the concatenation of all of its
      * component animations.
      *
+     * @see android.view.animation.Animation#getTransformationAt
+     * @hide
+     */
+    @Override
+    public void getTransformationAt(float interpolatedTime, Transformation t) {
+        final Transformation temp = mTempTransformation;
+
+        for (int i = mAnimations.size() - 1; i >= 0; --i) {
+            final Animation a = mAnimations.get(i);
+
+            temp.clear();
+            a.getTransformationAt(interpolatedTime, t);
+            t.compose(temp);
+        }
+    }
+
+    /**
+     * The transformation of an animation set is the concatenation of all of its
+     * component animations.
+     *
      * @see android.view.animation.Animation#getTransformation
      */
     @Override
@@ -517,4 +537,15 @@
     public boolean willChangeBounds() {
         return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
     }
+
+    /** @hide */
+    @Override
+    public boolean hasExtension() {
+        for (Animation animation : mAnimations) {
+            if (animation.hasExtension()) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 7ce0f45..7d1dc76 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -190,6 +190,8 @@
                 anim = new TranslateAnimation(c, attrs);
             } else if (name.equals("cliprect")) {
                 anim = new ClipRectAnimation(c, attrs);
+            } else if (name.equals("extend")) {
+                anim = new ExtendAnimation(c, attrs);
             } else {
                 throw new RuntimeException("Unknown animation name: " + parser.getName());
             }
diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java
new file mode 100644
index 0000000..fd627e5
--- /dev/null
+++ b/core/java/android/view/animation/ExtendAnimation.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Insets;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the outset of an object.
+ *
+ * @hide
+ */
+public class ExtendAnimation extends Animation {
+    protected Insets mFromInsets = Insets.NONE;
+    protected Insets mToInsets = Insets.NONE;
+
+    private int mFromLeftType = ABSOLUTE;
+    private int mFromTopType = ABSOLUTE;
+    private int mFromRightType = ABSOLUTE;
+    private int mFromBottomType = ABSOLUTE;
+
+    private int mToLeftType = ABSOLUTE;
+    private int mToTopType = ABSOLUTE;
+    private int mToRightType = ABSOLUTE;
+    private int mToBottomType = ABSOLUTE;
+
+    private float mFromLeftValue;
+    private float mFromTopValue;
+    private float mFromRightValue;
+    private float mFromBottomValue;
+
+    private float mToLeftValue;
+    private float mToTopValue;
+    private float mToRightValue;
+    private float mToBottomValue;
+
+    /**
+     * Constructor used when an ExtendAnimation is loaded from a resource.
+     *
+     * @param context Application context to use
+     * @param attrs Attribute set from which to read values
+     */
+    public ExtendAnimation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ExtendAnimation);
+
+        Description d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ExtendAnimation_fromExtendLeft));
+        mFromLeftType = d.type;
+        mFromLeftValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ExtendAnimation_fromExtendTop));
+        mFromTopType = d.type;
+        mFromTopValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ExtendAnimation_fromExtendRight));
+        mFromRightType = d.type;
+        mFromRightValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ExtendAnimation_fromExtendBottom));
+        mFromBottomType = d.type;
+        mFromBottomValue = d.value;
+
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ExtendAnimation_toExtendLeft));
+        mToLeftType = d.type;
+        mToLeftValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ExtendAnimation_toExtendTop));
+        mToTopType = d.type;
+        mToTopValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ExtendAnimation_toExtendRight));
+        mToRightType = d.type;
+        mToRightValue = d.value;
+
+        d = Description.parseValue(a.peekValue(
+                com.android.internal.R.styleable.ExtendAnimation_toExtendBottom));
+        mToBottomType = d.type;
+        mToBottomValue = d.value;
+
+        a.recycle();
+    }
+
+    /**
+     * Constructor to use when building an ExtendAnimation from code
+     *
+     * @param fromInsets the insets to animate from
+     * @param toInsets the insets to animate to
+     */
+    public ExtendAnimation(Insets fromInsets, Insets toInsets) {
+        if (fromInsets == null || toInsets == null) {
+            throw new RuntimeException("Expected non-null animation outsets");
+        }
+        mFromLeftValue = -fromInsets.left;
+        mFromTopValue = -fromInsets.top;
+        mFromRightValue = -fromInsets.right;
+        mFromBottomValue = -fromInsets.bottom;
+
+        mToLeftValue = -toInsets.left;
+        mToTopValue = -toInsets.top;
+        mToRightValue = -toInsets.right;
+        mToBottomValue = -toInsets.bottom;
+    }
+
+    /**
+     * Constructor to use when building an ExtendAnimation from code
+     */
+    public ExtendAnimation(int fromL, int fromT, int fromR, int fromB,
+            int toL, int toT, int toR, int toB) {
+        this(Insets.of(-fromL, -fromT, -fromR, -fromB), Insets.of(-toL, -toT, -toR, -toB));
+    }
+
+    @Override
+    protected void applyTransformation(float it, Transformation tr) {
+        int l = mFromInsets.left + (int) ((mToInsets.left - mFromInsets.left) * it);
+        int t = mFromInsets.top + (int) ((mToInsets.top - mFromInsets.top) * it);
+        int r = mFromInsets.right + (int) ((mToInsets.right - mFromInsets.right) * it);
+        int b = mFromInsets.bottom + (int) ((mToInsets.bottom - mFromInsets.bottom) * it);
+        tr.setInsets(l, t, r, b);
+    }
+
+    @Override
+    public boolean willChangeTransformationMatrix() {
+        return false;
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasExtension() {
+        return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0
+                || mFromInsets.bottom < 0;
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+        // We remove any negative extension (i.e. positive insets) and set those to 0
+        mFromInsets = Insets.min(Insets.of(
+                    -(int) resolveSize(mFromLeftType, mFromLeftValue, width, parentWidth),
+                    -(int) resolveSize(mFromTopType, mFromTopValue, height, parentHeight),
+                    -(int) resolveSize(mFromRightType, mFromRightValue, width, parentWidth),
+                    -(int) resolveSize(mFromBottomType, mFromBottomValue, height, parentHeight)
+                ), Insets.NONE);
+        mToInsets = Insets.min(Insets.of(
+                    -(int) resolveSize(mToLeftType, mToLeftValue, width, parentWidth),
+                    -(int) resolveSize(mToTopType, mToTopValue, height, parentHeight),
+                    -(int) resolveSize(mToRightType, mToRightValue, width, parentWidth),
+                    -(int) resolveSize(mToBottomType, mToBottomValue, height, parentHeight)
+                ), Insets.NONE);
+    }
+}
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index b35a66e..bd62308 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FloatRange;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 
@@ -53,6 +54,8 @@
     private boolean mHasClipRect;
     private Rect mClipRect = new Rect();
 
+    private Insets mInsets = Insets.NONE;
+
     /**
      * Creates a new transformation with alpha = 1 and the identity matrix.
      */
@@ -132,8 +135,9 @@
                 setClipRect(bounds);
             }
         }
+        setInsets(Insets.add(getInsets(), t.getInsets()));
     }
-    
+
     /**
      * Like {@link #compose(Transformation)} but does this.postConcat(t) of
      * the transformation matrix.
@@ -160,7 +164,7 @@
     public Matrix getMatrix() {
         return mMatrix;
     }
-    
+
     /**
      * Sets the degree of transparency
      * @param alpha 1.0 means fully opaqe and 0.0 means fully transparent
@@ -170,6 +174,13 @@
     }
 
     /**
+     * @return The degree of transparency
+     */
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    /**
      * Sets the current Transform's clip rect
      * @hide
      */
@@ -203,12 +214,29 @@
     }
 
     /**
-     * @return The degree of transparency
+     * Sets the current Transform's insets
+     * @hide
      */
-    public float getAlpha() {
-        return mAlpha;
+    public void setInsets(Insets insets) {
+        mInsets = insets;
     }
-    
+
+    /**
+     * Sets the current Transform's insets
+     * @hide
+     */
+    public void setInsets(int left, int top, int right, int bottom) {
+        mInsets = Insets.of(left, top, right, bottom);
+    }
+
+    /**
+     * Returns the current Transform's outset rect
+     * @hide
+     */
+    public Insets getInsets() {
+        return mInsets;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(64);
@@ -216,7 +244,7 @@
         toShortString(sb);
         return sb.toString();
     }
-    
+
     /**
      * Return a string representation of the transformation in a compact form.
      */
@@ -225,7 +253,7 @@
         toShortString(sb);
         return sb.toString();
     }
-    
+
     /**
      * @hide
      */
@@ -234,7 +262,7 @@
         sb.append(" matrix="); sb.append(mMatrix.toShortString());
         sb.append('}');
     }
-    
+
     /**
      * Print short string, to optimize dumping.
      * @hide
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 9552691..2134d81 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -291,6 +291,8 @@
      * <p>Typically used to change the context associated with the default session from an activity.
      */
     public final void setContentCaptureContext(@Nullable ContentCaptureContext context) {
+        if (!isContentCaptureEnabled()) return;
+
         mClientContext = context;
         updateContentCaptureContext(context);
     }
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index ff6903e8..08cc31c 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -105,12 +105,14 @@
      *                             current IME.
      * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME.
      * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME.
+     * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+     *                                            shown while the IME is shown.
      * @hide
      */
     @MainThread
     default void initializeInternal(IBinder token,
             IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
-            boolean stylusHwSupported) {
+            boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) {
         attachToken(token);
     }
 
@@ -229,6 +231,8 @@
      *                        the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
      *                        long as your implementation of {@link InputMethod} relies on such
      *                        IPCs
+     * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+     *                                            shown while the IME is shown.
      * @see #startInput(InputConnection, EditorInfo)
      * @see #restartInput(InputConnection, EditorInfo)
      * @see EditorInfo
@@ -237,7 +241,7 @@
     @MainThread
     default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
             @NonNull EditorInfo editorInfo, boolean restarting,
-            @NonNull IBinder startInputToken) {
+            @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) {
         if (restarting) {
             restartInput(inputConnection, editorInfo);
         } else {
@@ -246,6 +250,18 @@
     }
 
     /**
+     * Notifies that whether the IME should show the IME switcher or not is being changed.
+     *
+     * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+     *                                            shown while the IME is shown.
+     * @hide
+     */
+    @MainThread
+    default void onShouldShowImeSwitcherWhenImeIsShownChanged(
+            boolean shouldShowImeSwitcherWhenImeIsShown) {
+    }
+
+    /**
      * Create a new {@link InputMethodSession} that can be handed to client
      * applications for interacting with the input method.  You can later
      * use {@link #revokeSession(InputMethodSession)} to destroy the session
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c6f64f4..b00a382 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -301,6 +301,13 @@
     public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4;
 
     /**
+     * This mask determines which flags are propagated to nested RemoteViews (either added by
+     * addView, or set as portrait/landscape/sized RemoteViews).
+     */
+    static final int FLAG_MASK_TO_PROPAGATE =
+            FLAG_WIDGET_IS_COLLECTION_CHILD | FLAG_USE_LIGHT_BACKGROUND_LAYOUT;
+
+    /**
      * A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is
      * intentionally a different instance in order to trick Bundle reader so that it doesn't allow
      * lazy initialization.
@@ -467,6 +474,18 @@
      */
     public void addFlags(@ApplyFlags int flags) {
         mApplyFlags = mApplyFlags | flags;
+
+        int flagsToPropagate = flags & FLAG_MASK_TO_PROPAGATE;
+        if (flagsToPropagate != 0) {
+            if (hasSizedRemoteViews()) {
+                for (RemoteViews remoteView : mSizedRemoteViews) {
+                    remoteView.addFlags(flagsToPropagate);
+                }
+            } else if (hasLandscapeAndPortraitLayouts()) {
+                mLandscape.addFlags(flagsToPropagate);
+                mPortrait.addFlags(flagsToPropagate);
+            }
+        }
     }
 
     /**
@@ -2407,6 +2426,10 @@
             // will return -1.
             final int nextChild = getNextRecyclableChild(target);
             RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context);
+
+            int flagsToPropagate = mApplyFlags & FLAG_MASK_TO_PROPAGATE;
+            if (flagsToPropagate != 0) rvToApply.addFlags(flagsToPropagate);
+
             if (nextChild >= 0 && mStableId != NO_ID) {
                 // At that point, the views starting at index nextChild are the ones recyclable but
                 // not yet recycled. All views added on that round of application are placed before.
@@ -2419,8 +2442,8 @@
                             target.removeViews(nextChild, recycledViewIndex - nextChild);
                         }
                         setNextRecyclableChild(target, nextChild + 1, target.getChildCount());
-                        rvToApply.reapply(context, child, handler, null /* size */, colorResources,
-                                false /* topLevel */);
+                        rvToApply.reapplyNestedViews(context, child, rootParent, handler,
+                                null /* size */, colorResources);
                         return;
                     }
                     // If we cannot recycle the views, we still remove all views in between to
@@ -2431,8 +2454,8 @@
             // If we cannot recycle, insert the new view before the next recyclable child.
 
             // Inflate nested views and add as children
-            View nestedView = rvToApply.apply(context, target, handler, null /* size */,
-                    colorResources);
+            View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler,
+                    null /* size */, colorResources);
             if (mStableId != NO_ID) {
                 setStableId(nestedView, mStableId);
             }
@@ -3780,7 +3803,7 @@
      * @param parcel
      */
     public RemoteViews(Parcel parcel) {
-        this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0);
+        this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
     }
 
     private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
@@ -5580,6 +5603,16 @@
         return result;
     }
 
+    private View applyNestedViews(Context context, ViewGroup directParent,
+            ViewGroup rootParent, InteractionHandler handler, SizeF size,
+            ColorResources colorResources) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+
+        View result = inflateView(context, rvToApply, directParent, 0, colorResources);
+        rvToApply.performApply(result, rootParent, handler, colorResources);
+        return result;
+    }
+
     private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
         return inflateView(context, rv, parent, 0, null);
     }
@@ -5895,6 +5928,12 @@
         }
     }
 
+    private void reapplyNestedViews(Context context, View v, ViewGroup rootParent,
+            InteractionHandler handler, SizeF size, ColorResources colorResources) {
+        RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
+        rvToApply.performApply(v, rootParent, handler, colorResources);
+    }
+
     /**
      * Applies all the actions to the provided view, moving as much of the task on the background
      * thread as possible.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 41c5401..0fe2ed5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4461,7 +4461,7 @@
      * pixel" units.  This size is adjusted based on the current density and
      * user font size preference.
      *
-     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
+     * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
      *
      * @param size The scaled pixel size.
      *
@@ -4476,7 +4476,7 @@
      * Set the default text size to a given unit and value. See {@link
      * TypedValue} for the possible dimension units.
      *
-     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
+     * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
      *
      * @param unit The desired dimension unit.
      * @param size The desired size in the given units.
@@ -12289,6 +12289,7 @@
                     EXTRA_DATA_RENDERING_INFO_KEY,
                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
             ));
+            info.setTextSelectable(isTextSelectable());
         } else {
             info.setAvailableExtraData(Arrays.asList(
                     EXTRA_DATA_RENDERING_INFO_KEY
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 9648008..629a1b3 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -113,7 +113,7 @@
     void notePhoneOn();
     void notePhoneOff();
     void notePhoneSignalStrength(in SignalStrength signalStrength);
-    void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType);
+    void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrFrequency);
     void notePhoneState(int phoneState);
     void noteWifiOn();
     void noteWifiOff();
@@ -145,6 +145,8 @@
     long getAwakeTimeBattery();
     long getAwakeTimePlugged();
 
+    void noteBluetoothOn(int uid, int reason, String packageName);
+    void noteBluetoothOff(int uid, int reason, String packageName);
     void noteBleScanStarted(in WorkSource ws, boolean isUnoptimized);
     void noteBleScanStopped(in WorkSource ws, boolean isUnoptimized);
     void noteBleScanReset();
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 347153c..cdb69e5 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1011,7 +1011,9 @@
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         ViewPager viewPager = findViewById(R.id.profile_pager);
-        outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+        if (viewPager != null) {
+            outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+        }
     }
 
     @Override
@@ -1019,7 +1021,9 @@
         super.onRestoreInstanceState(savedInstanceState);
         resetButtonBar();
         ViewPager viewPager = findViewById(R.id.profile_pager);
-        viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+        if (viewPager != null) {
+            viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+        }
         mMultiProfilePagerAdapter.clearInactiveProfileCache();
     }
 
@@ -1568,6 +1572,11 @@
             rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
         }
 
+        if (shouldUseMiniResolver()) {
+            configureMiniResolverContent();
+            return false;
+        }
+
         if (useLayoutWithDefault()) {
             mLayoutId = R.layout.resolver_list_with_default;
         } else {
@@ -1578,6 +1587,72 @@
         return postRebuildList(rebuildCompleted);
     }
 
+    private void configureMiniResolverContent() {
+        mLayoutId = R.layout.miniresolver;
+        setContentView(mLayoutId);
+
+        DisplayResolveInfo sameProfileResolveInfo =
+                mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0);
+        boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
+
+        DisplayResolveInfo otherProfileResolveInfo =
+                mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList.get(0);
+        ImageView icon = findViewById(R.id.icon);
+        // TODO: Set icon drawable to app icon.
+
+        ((TextView) findViewById(R.id.open_cross_profile)).setText(
+                getResources().getString(
+                        inWorkProfile ? R.string.miniresolver_open_in_personal
+                                : R.string.miniresolver_open_in_work,
+                        otherProfileResolveInfo.getDisplayLabel()));
+        ((Button) findViewById(R.id.use_same_profile_browser)).setText(
+                inWorkProfile ? R.string.miniresolver_use_work_browser
+                        : R.string.miniresolver_use_personal_browser);
+
+        findViewById(R.id.use_same_profile_browser).setOnClickListener(
+                v -> safelyStartActivity(sameProfileResolveInfo));
+
+        findViewById(R.id.button_open).setOnClickListener(v -> {
+            Intent intent = otherProfileResolveInfo.getResolvedIntent();
+            if (intent != null) {
+                prepareIntentForCrossProfileLaunch(intent);
+            }
+            safelyStartActivityInternal(otherProfileResolveInfo,
+                    mMultiProfilePagerAdapter.getInactiveListAdapter().mResolverListController
+                            .getUserHandle());
+        });
+    }
+
+    private boolean shouldUseMiniResolver() {
+        if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
+                || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+            return false;
+        }
+        List<DisplayResolveInfo> sameProfileList =
+                mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList;
+        List<DisplayResolveInfo> otherProfileList =
+                mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList;
+
+        if (otherProfileList.size() != 1) {
+            Log.d(TAG, "Found " + otherProfileList.size() + " resolvers in the other profile");
+            return false;
+        }
+
+        if (otherProfileList.get(0).getResolveInfo().handleAllWebDataURI) {
+            Log.d(TAG, "Other profile is a web browser");
+            return false;
+        }
+
+        for (DisplayResolveInfo info : sameProfileList) {
+            if (!info.getResolveInfo().handleAllWebDataURI) {
+                Log.d(TAG, "Non-browser found in this profile");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     /**
      * Finishing procedures to be performed after the list has been rebuilt.
      * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/InstallLocationUtils.java
similarity index 87%
rename from core/java/com/android/internal/content/PackageHelper.java
rename to core/java/com/android/internal/content/InstallLocationUtils.java
index c2f2052..c456cf3 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/InstallLocationUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.content;
 
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
 
 import android.content.Context;
@@ -53,7 +54,7 @@
  * and media container service transports.
  * Some utility methods to invoke StorageManagerService api.
  */
-public class PackageHelper {
+public class InstallLocationUtils {
     public static final int RECOMMEND_INSTALL_INTERNAL = 1;
     public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
     public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
@@ -89,9 +90,13 @@
      */
     public static abstract class TestableInterface {
         abstract public StorageManager getStorageManager(Context context);
+
         abstract public boolean getForceAllowOnExternalSetting(Context context);
+
         abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
+
         abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
+
         abstract public File getDataDirectory();
     }
 
@@ -150,11 +155,11 @@
     /**
      * Given a requested {@link PackageInfo#installLocation} and calculated
      * install size, pick the actual volume to install the app. Only considers
-     * internal and private volumes, and prefers to keep an existing package on
+     * internal and private volumes, and prefers to keep an existing package onocation
      * its current volume.
      *
      * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
-     *         for internal storage.
+     * for internal storage.
      */
     public static String resolveInstallVolume(Context context, SessionParams params)
             throws IOException {
@@ -316,21 +321,6 @@
                 && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
     }
 
-    @Deprecated
-    public static int resolveInstallLocation(Context context, String packageName,
-            int installLocation, long sizeBytes, int installFlags) {
-        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
-        params.appPackageName = packageName;
-        params.installLocation = installLocation;
-        params.sizeBytes = sizeBytes;
-        params.installFlags = installFlags;
-        try {
-            return resolveInstallLocation(context, params);
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     /**
      * Given a requested {@link PackageInfo#installLocation} and calculated
      * install size, pick the actual location to install the app.
@@ -393,24 +383,24 @@
             // and will fall through to return INSUFFICIENT_STORAGE
             if (fitsOnInternal) {
                 return (ephemeral)
-                        ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
-                        : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                        ? InstallLocationUtils.RECOMMEND_INSTALL_EPHEMERAL
+                        : InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
             }
         } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
             if (fitsOnExternal) {
-                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
             }
         }
 
         if (checkBoth) {
             if (fitsOnInternal) {
-                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
             } else if (fitsOnExternal) {
-                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
             }
         }
 
-        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+        return InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
     }
 
     @Deprecated
@@ -476,4 +466,48 @@
             return 0;
         }
     }
+
+    public static int installLocationPolicy(int installLocation, int recommendedInstallLocation,
+            int installFlags, boolean installedPkgIsSystem, boolean installedPackageOnExternal) {
+        if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+            // Invalid install. Return error code
+            return RECOMMEND_FAILED_ALREADY_EXISTS;
+        }
+        // Check for updated system application.
+        if (installedPkgIsSystem) {
+            return RECOMMEND_INSTALL_INTERNAL;
+        }
+        // If current upgrade specifies particular preference
+        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+            // Application explicitly specified internal.
+            return RECOMMEND_INSTALL_INTERNAL;
+        } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+            // App explicitly prefers external. Let policy decide
+            return recommendedInstallLocation;
+        } else {
+            // Prefer previous location
+            if (installedPackageOnExternal) {
+                return RECOMMEND_INSTALL_EXTERNAL;
+            }
+            return RECOMMEND_INSTALL_INTERNAL;
+        }
+    }
+
+    public static int getInstallationErrorCode(int loc) {
+        if (loc == RECOMMEND_FAILED_INVALID_LOCATION) {
+            return PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+        } else if (loc == RECOMMEND_FAILED_ALREADY_EXISTS) {
+            return PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+        } else if (loc == RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+            return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+        } else if (loc == RECOMMEND_FAILED_INVALID_APK) {
+            return PackageManager.INSTALL_FAILED_INVALID_APK;
+        } else if (loc == RECOMMEND_FAILED_INVALID_URI) {
+            return PackageManager.INSTALL_FAILED_INVALID_URI;
+        } else if (loc == RECOMMEND_MEDIA_UNAVAILABLE) {
+            return PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
+        } else {
+            return INSTALL_SUCCEEDED;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 25ee2d0..c0fec62 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -69,10 +69,14 @@
 import android.os.connectivity.WifiActivityEnergyInfo;
 import android.os.connectivity.WifiBatteryStats;
 import android.provider.Settings;
+import android.telephony.Annotation.NetworkType;
 import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
+import android.telephony.ServiceState.RegState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -935,6 +939,119 @@
     final StopwatchTimer[] mPhoneDataConnectionsTimer =
             new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
 
+    @RadioAccessTechnology
+    int mActiveRat = RADIO_ACCESS_TECHNOLOGY_OTHER;
+
+    private static class RadioAccessTechnologyBatteryStats {
+        /**
+         * This RAT is currently being used.
+         */
+        private boolean mActive = false;
+        /**
+         * Current active frequency range for this RAT.
+         */
+        @ServiceState.FrequencyRange
+        private int mFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+        /**
+         * Current signal strength for this RAT.
+         */
+        private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        /**
+         * Timers for each combination of frequency range and signal strength.
+         */
+        public final StopwatchTimer[][] perStateTimers;
+
+        RadioAccessTechnologyBatteryStats(int freqCount, Clock clock, TimeBase timeBase) {
+            perStateTimers =
+                    new StopwatchTimer[freqCount][CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+            for (int i = 0; i < freqCount; i++) {
+                for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+                    perStateTimers[i][j] = new StopwatchTimer(clock, null, -1, null, timeBase);
+                }
+            }
+        }
+
+        /**
+         * Note this RAT is currently being used.
+         */
+        public void noteActive(boolean active, long elapsedRealtimeMs) {
+            if (mActive == active) return;
+            mActive = active;
+            if (mActive) {
+                perStateTimers[mFrequencyRange][mSignalStrength].startRunningLocked(
+                        elapsedRealtimeMs);
+            } else {
+                perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(
+                        elapsedRealtimeMs);
+            }
+        }
+
+        /**
+         * Note current frequency range has changed.
+         */
+        public void noteFrequencyRange(@ServiceState.FrequencyRange int frequencyRange,
+                long elapsedRealtimeMs) {
+            if (mFrequencyRange == frequencyRange) return;
+
+            if (!mActive) {
+                // RAT not in use, note the frequency change and move on.
+                mFrequencyRange = frequencyRange;
+                return;
+            }
+            perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+            perStateTimers[frequencyRange][mSignalStrength].startRunningLocked(elapsedRealtimeMs);
+            mFrequencyRange = frequencyRange;
+        }
+
+        /**
+         * Note current signal strength has changed.
+         */
+        public void noteSignalStrength(int signalStrength, long elapsedRealtimeMs) {
+            if (mSignalStrength == signalStrength) return;
+
+            if (!mActive) {
+                // RAT not in use, note the signal strength change and move on.
+                mSignalStrength = signalStrength;
+                return;
+            }
+            perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+            perStateTimers[mFrequencyRange][signalStrength].startRunningLocked(elapsedRealtimeMs);
+            mSignalStrength = signalStrength;
+        }
+
+        /**
+         * Reset display timers.
+         */
+        public void reset(long elapsedRealtimeUs) {
+            final int size = perStateTimers.length;
+            for (int i = 0; i < size; i++) {
+                for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+                    perStateTimers[i][j].reset(false, elapsedRealtimeUs);
+                }
+            }
+        }
+    }
+
+    /**
+     * Number of frequency ranges, keep in sync with {@link ServiceState.FrequencyRange}
+     */
+    private static final int NR_FREQUENCY_COUNT = 5;
+
+    RadioAccessTechnologyBatteryStats[] mPerRatBatteryStats =
+            new RadioAccessTechnologyBatteryStats[RADIO_ACCESS_TECHNOLOGY_COUNT];
+
+    @GuardedBy("this")
+    private RadioAccessTechnologyBatteryStats getRatBatteryStatsLocked(
+            @RadioAccessTechnology int rat) {
+        RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+        if (stats == null) {
+            final int freqCount = rat == RADIO_ACCESS_TECHNOLOGY_NR ? NR_FREQUENCY_COUNT : 1;
+            stats = new RadioAccessTechnologyBatteryStats(freqCount, mClock, mOnBatteryTimeBase);
+            mPerRatBatteryStats[rat] = stats;
+        }
+        return stats;
+    }
+
     final LongSamplingCounter[] mNetworkByteActivityCounters =
             new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
 
@@ -1896,17 +2013,15 @@
         private final TimeBase mTimeBase;
         private final LongArrayMultiStateCounter mCounter;
 
-        private TimeInFreqMultiStateCounter(TimeBase timeBase, Parcel in, long timestampMs) {
-            mTimeBase = timeBase;
-            mCounter = LongArrayMultiStateCounter.CREATOR.createFromParcel(in);
-            mCounter.setEnabled(mTimeBase.isRunning(), timestampMs);
-            timeBase.add(this);
-        }
-
         private TimeInFreqMultiStateCounter(TimeBase timeBase, int stateCount, int cpuFreqCount,
                 long timestampMs) {
+            this(timeBase, new LongArrayMultiStateCounter(stateCount, cpuFreqCount), timestampMs);
+        }
+
+        private TimeInFreqMultiStateCounter(TimeBase timeBase, LongArrayMultiStateCounter counter,
+                long timestampMs) {
             mTimeBase = timeBase;
-            mCounter = new LongArrayMultiStateCounter(stateCount, cpuFreqCount);
+            mCounter = counter;
             mCounter.setEnabled(mTimeBase.isRunning(), timestampMs);
             timeBase.add(this);
         }
@@ -1915,6 +2030,19 @@
             mCounter.writeToParcel(out, 0);
         }
 
+        @Nullable
+        private static TimeInFreqMultiStateCounter readFromParcel(Parcel in, TimeBase timeBase,
+                int stateCount, int cpuFreqCount, long timestampMs) {
+            // Read the object from the Parcel, whether it's usable or not
+            LongArrayMultiStateCounter counter =
+                    LongArrayMultiStateCounter.CREATOR.createFromParcel(in);
+            if (counter.getStateCount() != stateCount
+                    || counter.getArrayLength() != cpuFreqCount) {
+                return null;
+            }
+            return new TimeInFreqMultiStateCounter(timeBase, counter, timestampMs);
+        }
+
         @Override
         public void onTimeStarted(long elapsedRealtimeUs, long baseUptimeUs, long baseRealtimeUs) {
             mCounter.setEnabled(true, elapsedRealtimeUs / 1000);
@@ -5886,6 +6014,10 @@
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mMobileRadioPowerState = powerState;
+
+            // Inform current RatBatteryStats that the modem active state might have changed.
+            getRatBatteryStatsLocked(mActiveRat).noteActive(active, elapsedRealtimeMs);
+
             if (active) {
                 mMobileRadioActiveTimer.startRunningLocked(elapsedRealtimeMs);
                 mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtimeMs);
@@ -6307,21 +6439,86 @@
     @GuardedBy("this")
     public void notePhoneSignalStrengthLocked(SignalStrength signalStrength,
             long elapsedRealtimeMs, long uptimeMs) {
-        // Bin the strength.
-        int bin = signalStrength.getLevel();
-        updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, bin,
+        final int overallSignalStrength = signalStrength.getLevel();
+        final SparseIntArray perRatSignalStrength = new SparseIntArray(
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT);
+
+        // Extract signal strength level for each RAT.
+        final List<CellSignalStrength> cellSignalStrengths =
+                signalStrength.getCellSignalStrengths();
+        final int size = cellSignalStrengths.size();
+        for (int i = 0; i < size; i++) {
+            CellSignalStrength cellSignalStrength = cellSignalStrengths.get(i);
+            // Map each CellSignalStrength to a BatteryStats.RadioAccessTechnology
+            final int ratType;
+            final int level;
+            if (cellSignalStrength instanceof CellSignalStrengthNr) {
+                ratType = RADIO_ACCESS_TECHNOLOGY_NR;
+                level = cellSignalStrength.getLevel();
+            } else if (cellSignalStrength instanceof CellSignalStrengthLte) {
+                ratType = RADIO_ACCESS_TECHNOLOGY_LTE;
+                level = cellSignalStrength.getLevel();
+            } else {
+                ratType = RADIO_ACCESS_TECHNOLOGY_OTHER;
+                level = cellSignalStrength.getLevel();
+            }
+
+            // According to SignalStrength#getCellSignalStrengths(), multiple of the same
+            // cellSignalStrength can be present. Just take the highest level one for each RAT.
+            if (perRatSignalStrength.get(ratType, -1) < level) {
+                perRatSignalStrength.put(ratType, level);
+            }
+        }
+
+        notePhoneSignalStrengthLocked(overallSignalStrength, perRatSignalStrength,
+                elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
+     * Note phone signal strength change, including per RAT signal strength.
+     *
+     * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+     * @param perRatSignalStrength signal strength of available RATs
+     */
+    @GuardedBy("this")
+    public void notePhoneSignalStrengthLocked(int signalStrength,
+            SparseIntArray perRatSignalStrength) {
+        notePhoneSignalStrengthLocked(signalStrength, perRatSignalStrength,
+                mClock.elapsedRealtime(), mClock.uptimeMillis());
+    }
+
+    /**
+     * Note phone signal strength change, including per RAT signal strength.
+     *
+     * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+     * @param perRatSignalStrength signal strength of available RATs
+     */
+    @GuardedBy("this")
+    public void notePhoneSignalStrengthLocked(int signalStrength,
+            SparseIntArray perRatSignalStrength,
+            long elapsedRealtimeMs, long uptimeMs) {
+        // Note each RAT's signal strength.
+        final int size = perRatSignalStrength.size();
+        for (int i = 0; i < size; i++) {
+            final int rat = perRatSignalStrength.keyAt(i);
+            final int ratSignalStrength = perRatSignalStrength.valueAt(i);
+            getRatBatteryStatsLocked(rat).noteSignalStrength(ratSignalStrength, elapsedRealtimeMs);
+        }
+        updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, signalStrength,
                 elapsedRealtimeMs, uptimeMs);
     }
 
     @UnsupportedAppUsage
     @GuardedBy("this")
-    public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType) {
-        notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
+    public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+            @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency) {
+        notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrFrequency,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
     @GuardedBy("this")
-    public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType,
+    public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+            @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency,
             long elapsedRealtimeMs, long uptimeMs) {
         // BatteryStats uses 0 to represent no network type.
         // Telephony does not have a concept of no network type, and uses 0 to represent unknown.
@@ -6344,6 +6541,13 @@
                 }
             }
         }
+
+        final int newRat = mapNetworkTypeToRadioAccessTechnology(bin);
+        if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) {
+            // Note possible frequency change for the NR RAT.
+            getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs);
+        }
+
         if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
         if (mPhoneDataConnectionType != bin) {
             mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
@@ -6357,6 +6561,45 @@
             }
             mPhoneDataConnectionType = bin;
             mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtimeMs);
+
+            if (mActiveRat != newRat) {
+                getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs);
+                mActiveRat = newRat;
+            }
+            final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked();
+            getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs);
+        }
+    }
+
+    @RadioAccessTechnology
+    private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType) {
+        switch (dataType) {
+            case TelephonyManager.NETWORK_TYPE_NR:
+                return RADIO_ACCESS_TECHNOLOGY_NR;
+            case TelephonyManager.NETWORK_TYPE_LTE:
+                return RADIO_ACCESS_TECHNOLOGY_LTE;
+            case TelephonyManager.NETWORK_TYPE_UNKNOWN: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_GPRS: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EDGE: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_UMTS: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_CDMA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EVDO_0: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EVDO_A: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_1xRTT: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_HSDPA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_HSUPA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_HSPA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_IDEN: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EVDO_B: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_EHRPD: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_HSPAP: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_GSM: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_TD_SCDMA: //fallthrough
+            case TelephonyManager.NETWORK_TYPE_IWLAN: //fallthrough
+                return RADIO_ACCESS_TECHNOLOGY_OTHER;
+            default:
+                Slog.w(TAG, "Unhandled NetworkType (" + dataType + "), mapping to OTHER");
+                return RADIO_ACCESS_TECHNOLOGY_OTHER;
         }
     }
 
@@ -7731,6 +7974,23 @@
         return mPhoneDataConnectionsTimer[dataType];
     }
 
+    @Override public long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+            long elapsedRealtimeMs) {
+        final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+        if (stats == null) return 0L;
+
+        final int freqCount = stats.perStateTimers.length;
+        if (frequencyRange < 0 || frequencyRange >= freqCount) return 0L;
+
+        final StopwatchTimer[] strengthTimers = stats.perStateTimers[frequencyRange];
+        final int strengthCount = strengthTimers.length;
+        if (signalStrength < 0 || signalStrength >= strengthCount) return 0L;
+
+        return stats.perStateTimers[frequencyRange][signalStrength].getTotalTimeLocked(
+                elapsedRealtimeMs * 1000, STATS_SINCE_CHARGED) / 1000;
+    }
+
     @UnsupportedAppUsage
     @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
         return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
@@ -10535,25 +10795,18 @@
 
             stateCount = in.readInt();
             if (stateCount != 0) {
-                // Read the object from the Parcel, whether it's usable or not
-                TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
-                        mBsi.mOnBatteryTimeBase, in, timestampMs);
-                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
-                    mProcStateTimeMs = counter;
-                }
+                mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+                        mBsi.mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                        mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime());
             } else {
                 mProcStateTimeMs = null;
             }
 
             stateCount = in.readInt();
             if (stateCount != 0) {
-                // Read the object from the Parcel, whether it's usable or not
-                TimeInFreqMultiStateCounter counter =
-                        new TimeInFreqMultiStateCounter(
-                                mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs);
-                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
-                    mProcStateScreenOffTimeMs = counter;
-                }
+                mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+                        mBsi.mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                        mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime());
             } else {
                 mProcStateScreenOffTimeMs = null;
             }
@@ -12553,6 +12806,11 @@
             mNetworkByteActivityCounters[i].reset(false, elapsedRealtimeUs);
             mNetworkPacketActivityCounters[i].reset(false, elapsedRealtimeUs);
         }
+        for (int i = 0; i < RADIO_ACCESS_TECHNOLOGY_COUNT; i++) {
+            final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[i];
+            if (stats == null) continue;
+            stats.reset(elapsedRealtimeUs);
+        }
         mMobileRadioActiveTimer.reset(false, elapsedRealtimeUs);
         mMobileRadioActivePerAppTimer.reset(false, elapsedRealtimeUs);
         mMobileRadioActiveAdjustedTime.reset(false, elapsedRealtimeUs);
@@ -16107,6 +16365,11 @@
             BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt(
                     KEY_BATTERY_CHARGED_DELAY_MS,
                     DEFAULT_BATTERY_CHARGED_DELAY_MS);
+
+            if (mHandler.hasCallbacks(mDeferSetCharging)) {
+                mHandler.removeCallbacks(mDeferSetCharging);
+                mHandler.postDelayed(mDeferSetCharging, BATTERY_CHARGED_DELAY_MS);
+            }
         }
 
         private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
@@ -16906,12 +17169,10 @@
 
             stateCount = in.readInt();
             if (stateCount != 0) {
-                // Read the object from the Parcel, whether it's usable or not
-                TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
-                        mOnBatteryTimeBase, in, mClock.elapsedRealtime());
-                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
-                    u.mProcStateTimeMs = counter;
-                }
+                detachIfNotNull(u.mProcStateTimeMs);
+                u.mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+                        mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                        getCpuFreqCount(), mClock.elapsedRealtime());
             }
 
             detachIfNotNull(u.mProcStateScreenOffTimeMs);
@@ -16920,13 +17181,9 @@
             stateCount = in.readInt();
             if (stateCount != 0) {
                 detachIfNotNull(u.mProcStateScreenOffTimeMs);
-                // Read the object from the Parcel, whether it's usable or not
-                TimeInFreqMultiStateCounter counter =
-                        new TimeInFreqMultiStateCounter(
-                                mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime());
-                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
-                    u.mProcStateScreenOffTimeMs = counter;
-                }
+                u.mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+                        mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                        getCpuFreqCount(), mClock.elapsedRealtime());
             }
 
             if (in.readInt() != 0) {
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index be91aac..0a29fc52 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1159,6 +1159,17 @@
                 : Integer.compare(a.transactionCode, b.transactionCode);
     }
 
+    /** @hide */
+    public static void startForBluetooth(Context context) {
+        new BinderCallsStats.SettingsObserver(
+                    context,
+                    new BinderCallsStats(
+                            new BinderCallsStats.Injector(),
+                              com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH));
+
+    }
+
+
 
     /**
      * Settings observer for other processes (not system_server).
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index a50282e..2925341 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -285,6 +285,7 @@
     private Insets mBackgroundInsets = Insets.NONE;
     private Insets mLastBackgroundInsets = Insets.NONE;
     private boolean mDrawLegacyNavigationBarBackground;
+    private boolean mDrawLegacyNavigationBarBackgroundHandled;
 
     private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
 
@@ -1035,10 +1036,7 @@
     public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
         updateColorViews(null /* insets */, true /* animate */);
         if (mWindow != null) {
-            final Window.Callback callback = mWindow.getCallback();
-            if (callback != null) {
-                callback.onSystemBarAppearanceChanged(appearance);
-            }
+            mWindow.dispatchOnSystemBarAppearanceChanged(appearance);
         }
     }
 
@@ -1174,6 +1172,9 @@
             mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
                     && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
             if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
+                mDrawLegacyNavigationBarBackgroundHandled =
+                        mWindow.onDrawLegacyNavigationBarBackgroundChanged(
+                                mDrawLegacyNavigationBarBackground);
                 if (viewRoot != null) {
                     viewRoot.requestInvalidateRootRenderNode();
                 }
@@ -1266,7 +1267,7 @@
             }
         }
 
-        if (forceConsumingNavBar) {
+        if (forceConsumingNavBar && !mDrawLegacyNavigationBarBackgroundHandled) {
             mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset);
         } else {
             mBackgroundInsets = Insets.NONE;
@@ -2491,7 +2492,7 @@
     }
 
     private void drawLegacyNavigationBarBackground(RecordingCanvas canvas) {
-        if (!mDrawLegacyNavigationBarBackground) {
+        if (!mDrawLegacyNavigationBarBackground || mDrawLegacyNavigationBarBackgroundHandled) {
             return;
         }
         View v = mNavigationColorViewState.view;
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index faea7706e..37c96e7 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -29,7 +29,6 @@
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
-import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -99,6 +98,10 @@
 
     private static final String DEFAULT_PACKAGE = "android";
 
+    // TODO (b/215515255): remove once we full migrate to shell transitions
+    private static final boolean SHELL_TRANSITIONS_ENABLED =
+            SystemProperties.getBoolean("persist.debug.shell_transit", false);
+
     private final Context mContext;
     private final String mTag;
 
@@ -253,6 +256,9 @@
                 resId = ent.array.getResourceId(animAttr, 0);
             }
         }
+        if (!SHELL_TRANSITIONS_ENABLED) {
+            resId = updateToLegacyIfNeeded(resId);
+        }
         resId = updateToTranslucentAnimIfNeeded(resId, transit);
         if (ResourceId.isValid(resId)) {
             return loadAnimationSafely(context, resId, mTag);
@@ -260,6 +266,24 @@
         return null;
     }
 
+    /**
+     * Replace animations that are not compatible with the legacy transition system with ones that
+     * are compatible with it.
+     * TODO (b/215515255): remove once we full migrate to shell transitions
+     */
+    private int updateToLegacyIfNeeded(int anim) {
+        if (anim == R.anim.activity_open_enter) {
+            return R.anim.activity_open_enter_legacy;
+        } else if (anim == R.anim.activity_open_exit) {
+            return R.anim.activity_open_exit_legacy;
+        } else if (anim == R.anim.activity_close_enter) {
+            return R.anim.activity_close_enter_legacy;
+        } else if (anim == R.anim.activity_close_exit) {
+            return R.anim.activity_close_exit_legacy;
+        }
+        return anim;
+    }
+
     /** Load animation by attribute Id from a specific AnimationStyle resource. */
     @Nullable
     public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
@@ -915,7 +939,7 @@
      * animation.
      */
     public HardwareBuffer createCrossProfileAppsThumbnail(
-            @DrawableRes int thumbnailDrawableRes, Rect frame) {
+            Drawable thumbnailDrawable, Rect frame) {
         final int width = frame.width();
         final int height = frame.height();
 
@@ -924,14 +948,13 @@
         canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
         final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
-        final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes);
-        drawable.setBounds(
+        thumbnailDrawable.setBounds(
                 (width - thumbnailSize) / 2,
                 (height - thumbnailSize) / 2,
                 (width + thumbnailSize) / 2,
                 (height + thumbnailSize) / 2);
-        drawable.setTint(mContext.getColor(android.R.color.white));
-        drawable.draw(canvas);
+        thumbnailDrawable.setTint(mContext.getColor(android.R.color.white));
+        thumbnailDrawable.draw(canvas);
         picture.endRecording();
 
         return Bitmap.createBitmap(picture).getHardwareBuffer();
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a5cf7ce..51eb429 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -20,15 +20,18 @@
 import android.content.ComponentName;
 import android.graphics.drawable.Icon;
 import android.graphics.Rect;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.media.MediaRoute2Info;
 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;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.view.AppearanceRegion;
 
@@ -166,6 +169,8 @@
     * Used to hide the authentication dialog, e.g. when the application cancels authentication.
     */
     void hideAuthenticationDialog();
+    /* Used to notify the biometric service of events that occur outside of an operation. */
+    void setBiometicContextListener(in IBiometricContextListener listener);
 
     /**
      * Sets an instance of IUdfpsHbmListener for UdfpsController.
@@ -293,4 +298,15 @@
 
     void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback);
     void cancelRequestAddTile(in String packageName);
+
+    /** Notifies System UI about an update to the media tap-to-transfer sender state. */
+    void updateMediaTapToTransferSenderDisplay(
+        int displayState,
+        in MediaRoute2Info routeInfo,
+        in IUndoMediaTransferCallback undoCallback);
+
+    /** Notifies System UI about an update to the media tap-to-transfer receiver state. */
+    void updateMediaTapToTransferReceiverDisplay(
+        int displayState,
+        in MediaRoute2Info routeInfo);
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index accb986..0c45e5b 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -20,9 +20,11 @@
 import android.content.ComponentName;
 import android.graphics.drawable.Icon;
 import android.graphics.Rect;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.media.MediaRoute2Info;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -32,6 +34,7 @@
 import com.android.internal.statusbar.IAddTileResultCallback;
 import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBar;
+import com.android.internal.statusbar.IUndoMediaTransferCallback;
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.statusbar.StatusBarIconList;
@@ -125,6 +128,8 @@
     void onBiometricError(int modality, int error, int vendorCode);
     // Used to hide the authentication dialog, e.g. when the application cancels authentication
     void hideAuthenticationDialog();
+    // Used to notify the biometric service of events that occur outside of an operation.
+    void setBiometicContextListener(in IBiometricContextListener listener);
 
     /**
      * Sets an instance of IUdfpsHbmListener for UdfpsController.
@@ -193,4 +198,15 @@
     */
     void onSessionStarted(int sessionType, in InstanceId instanceId);
     void onSessionEnded(int sessionType, in InstanceId instanceId);
+
+    /** Notifies System UI about an update to the media tap-to-transfer sender state. */
+    void updateMediaTapToTransferSenderDisplay(
+        int displayState,
+        in MediaRoute2Info routeInfo,
+        in IUndoMediaTransferCallback undoCallback);
+
+    /** Notifies System UI about an update to the media tap-to-transfer receiver state. */
+    void updateMediaTapToTransferReceiverDisplay(
+        int displayState,
+        in MediaRoute2Info routeInfo);
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl b/core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl
similarity index 68%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
rename to core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl
index b47be87..3dd2980 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
+++ b/core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl
@@ -14,17 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.mediattt;
+package com.android.internal.statusbar;
 
 /**
- * An interface that will be invoked by System UI if the user choose to undo a transfer.
- *
- * Other services will implement this interface and System UI will invoke it.
+ * An interface that will be invoked if the user chooses to undo a transfer.
  */
-interface IUndoTransferCallback {
+interface IUndoMediaTransferCallback {
 
     /**
-     * Invoked by SystemUI when the user requests to undo the media transfer that just occurred.
+     * Invoked to notify callers that the user has chosen to undo the media transfer that just
+     * occurred.
      *
      * Implementors of this method are repsonsible for actually undoing the transfer.
      */
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index bfe4323..17b84ff 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.ColorInt;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -72,9 +73,30 @@
             // Return colored icon instead
             colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
         }
+        return getDefaultUserIconInColor(resources, resources.getColor(colorResId, null));
+    }
+
+    /**
+     * Returns a default user icon in a particular color.
+     *
+     * @param resources resources object to fetch the user icon
+     * @param color the color used for the icon
+     */
+    public static Drawable getDefaultUserIconInColor(Resources resources, @ColorInt int color) {
         Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
-        icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
+        icon.setColorFilter(color, Mode.SRC_IN);
         icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
         return icon;
     }
+
+    /**
+     * Returns an array containing colors to be used for default user icons.
+     */
+    public static int[] getUserIconColors(Resources resources) {
+        int[] result = new int[USER_ICON_COLORS.length];
+        for (int i = 0; i < result.length; i++) {
+            result[i] = resources.getColor(USER_ICON_COLORS[i], null);
+        }
+        return result;
+    }
 }
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index da24832..d2bc344 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -37,7 +37,8 @@
  */
 oneway interface IInputMethod {
     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-             int configChanges, boolean stylusHwSupported);
+             int configChanges, boolean stylusHwSupported,
+             boolean shouldShowImeSwitcherWhenImeIsShown);
 
     void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
             in IInlineSuggestionsRequestCallback cb);
@@ -47,7 +48,10 @@
     void unbindInput();
 
     void startInput(in IBinder startInputToken, in IInputContext inputContext,
-            in EditorInfo attribute, boolean restarting);
+            in EditorInfo attribute, boolean restarting,
+             boolean shouldShowImeSwitcherWhenImeIsShown);
+
+    void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown);
 
     void createSession(in InputChannel channel, IInputSessionCallback callback);
 
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 39f17e5..93864fa 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -237,6 +237,10 @@
     // be delivered anonymously even to apps which target O+.
     final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
 
+    // These are the packages that are exempted from the background restriction applied
+    // by the system automatically, i.e., due to high background current drain.
+    final ArraySet<String> mBgRestrictionExemption = new ArraySet<>();
+
     // These are the package names of apps which should be automatically granted domain verification
     // for all of their domains. The only way these apps can be overridden by the user is by
     // explicitly disabling overall link handling support in app info.
@@ -389,6 +393,10 @@
         return mAllowIgnoreLocationSettings;
     }
 
+    public ArraySet<String> getBgRestrictionExemption() {
+        return mBgRestrictionExemption;
+    }
+
     public ArraySet<String> getLinkedApps() {
         return mLinkedApps;
     }
@@ -1049,6 +1057,20 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "bg-restriction-exemption": {
+                        if (allowOverrideAppRestrictions) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mBgRestrictionExemption.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "default-enabled-vr-app": {
                         if (allowAppConfigs) {
                             String pkgname = parser.getAttributeValue(null, "package");
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 430d84e..8bb9a0a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -195,7 +195,6 @@
                 "android_util_FileObserver.cpp",
                 "android/opengl/poly_clip.cpp", // TODO: .arm
                 "android/opengl/util.cpp",
-                "android_server_NetworkManagementSocketTagger.cpp",
                 "android_ddm_DdmHandleNativeHeap.cpp",
                 "android_backup_BackupDataInput.cpp",
                 "android_backup_BackupDataOutput.cpp",
@@ -310,6 +309,8 @@
                 "libdl_android",
                 "libtimeinstate",
                 "server_configurable_flags",
+                // TODO: delete when ConnectivityT moves to APEX.
+                "libframework-connectivity-tiramisu-jni",
             ],
             export_shared_lib_headers: [
                 // our headers include libnativewindow's public headers
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f4296be..cde71cf 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -164,7 +164,6 @@
 extern int register_android_text_Hyphenator(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
 extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
-extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
 extern int register_android_backup_BackupDataInput(JNIEnv *env);
 extern int register_android_backup_BackupDataOutput(JNIEnv *env);
 extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
@@ -1623,7 +1622,6 @@
         REG_JNI(register_android_media_midi),
 
         REG_JNI(register_android_opengl_classes),
-        REG_JNI(register_android_server_NetworkManagementSocketTagger),
         REG_JNI(register_android_ddm_DdmHandleNativeHeap),
         REG_JNI(register_android_backup_BackupDataInput),
         REG_JNI(register_android_backup_BackupDataOutput),
diff --git a/core/jni/android/opengl/OWNERS b/core/jni/android/opengl/OWNERS
new file mode 100644
index 0000000..ce4b907
--- /dev/null
+++ b/core/jni/android/opengl/OWNERS
@@ -0,0 +1 @@
+file:/graphics/java/android/graphics/OWNERS
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 82601ba..d852265 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -643,6 +643,8 @@
             return (type == GL_UNSIGNED_SHORT_5_6_5 && internalformat == GL_RGB);
         case ANDROID_BITMAP_FORMAT_RGBA_F16:
             return (type == GL_HALF_FLOAT && internalformat == GL_RGBA16F);
+        case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+            return (type == GL_UNSIGNED_INT_2_10_10_10_REV && internalformat == GL_RGB10_A2);
         default:
             break;
     }
@@ -676,6 +678,8 @@
             return GL_RGB;
         case ANDROID_BITMAP_FORMAT_RGBA_F16:
             return GL_RGBA16F;
+        case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+            return GL_RGB10_A2;
         default:
             return -1;
     }
@@ -693,6 +697,8 @@
             return GL_UNSIGNED_SHORT_5_6_5;
         case ANDROID_BITMAP_FORMAT_RGBA_F16:
             return GL_HALF_FLOAT;
+        case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+            return GL_UNSIGNED_INT_2_10_10_10_REV;
         default:
             return -1;
     }
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index d039bcf..78b403c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -133,6 +133,18 @@
             MakeGlobalRefOrDie(_env, _env->CallObjectMethod(empty.get(), stringOffsets.intern));
 }
 
+uint64_t htonll(uint64_t ll) {
+    constexpr uint32_t kBytesToTest = 0x12345678;
+    constexpr uint8_t kFirstByte = (const uint8_t &)kBytesToTest;
+    constexpr bool kIsLittleEndian = kFirstByte == 0x78;
+
+    if constexpr (kIsLittleEndian) {
+        return static_cast<uint64_t>(htonl(ll & 0xffffffff)) << 32 | htonl(ll >> 32);
+    } else {
+        return ll;
+    }
+}
+
 static jstring getJavaInternedString(JNIEnv *env, const String8 &string) {
     if (string == "") {
         return gStringOffsets.emptyString;
@@ -193,7 +205,8 @@
         int32_t id = nativeSensor.getId();
         env->CallVoidMethod(sensor, sensorOffsets.setId, id);
         Sensor::uuid_t uuid = nativeSensor.getUuid();
-        env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]);
+        env->CallVoidMethod(sensor, sensorOffsets.setUuid, htonll(uuid.i64[0]),
+                            htonll(uuid.i64[1]));
     }
     return sensor;
 }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index e13b788..edc8c5b 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -204,6 +204,12 @@
 
 jclass gAudioProfileClass;
 jmethodID gAudioProfileCstor;
+static struct {
+    jfieldID mSamplingRates;
+    jfieldID mChannelMasks;
+    jfieldID mChannelIndexMasks;
+    jfieldID mEncapsulationType;
+} gAudioProfileFields;
 
 jclass gVibratorClass;
 static struct {
@@ -1288,7 +1294,7 @@
                            audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(),
                            jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType);
 
-    if (jAudioProfile == nullptr) {
+    if (*jAudioProfile == nullptr) {
         return AUDIO_JAVA_ERROR;
     }
 
@@ -1340,81 +1346,50 @@
         jStatus = (jint)AUDIO_JAVA_ERROR;
         goto exit;
     }
+
     for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) {
-        size_t numPositionMasks = 0;
-        size_t numIndexMasks = 0;
-        // count up how many masks are positional and indexed
-        for (size_t index = 0; index < nAudioPort->audio_profiles[i].num_channel_masks; index++) {
-            const audio_channel_mask_t mask = nAudioPort->audio_profiles[i].channel_masks[index];
-            if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
-                numIndexMasks++;
-            } else {
-                numPositionMasks++;
-            }
-        }
-
-        ScopedLocalRef<jintArray> jSamplingRates(env,
-                                                 env->NewIntArray(nAudioPort->audio_profiles[i]
-                                                                          .num_sample_rates));
-        ScopedLocalRef<jintArray> jChannelMasks(env, env->NewIntArray(numPositionMasks));
-        ScopedLocalRef<jintArray> jChannelIndexMasks(env, env->NewIntArray(numIndexMasks));
-        if (!jSamplingRates.get() || !jChannelMasks.get() || !jChannelIndexMasks.get()) {
+        jobject jAudioProfile = nullptr;
+        jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i],
+                                                useInMask);
+        if (jStatus != NO_ERROR) {
             jStatus = (jint)AUDIO_JAVA_ERROR;
             goto exit;
         }
+        env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile);
 
-        if (nAudioPort->audio_profiles[i].num_sample_rates) {
-            env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/,
-                                   nAudioPort->audio_profiles[i].num_sample_rates,
-                                   (jint *)nAudioPort->audio_profiles[i].sample_rates);
-        }
-
-        // put the masks in the output arrays
-        for (size_t maskIndex = 0, posMaskIndex = 0, indexedMaskIndex = 0;
-             maskIndex < nAudioPort->audio_profiles[i].num_channel_masks; maskIndex++) {
-            const audio_channel_mask_t mask =
-                    nAudioPort->audio_profiles[i].channel_masks[maskIndex];
-            if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
-                jint jMask = audio_channel_mask_get_bits(mask);
-                env->SetIntArrayRegion(jChannelIndexMasks.get(), indexedMaskIndex++, 1, &jMask);
-            } else {
-                jint jMask =
-                        useInMask ? inChannelMaskFromNative(mask) : outChannelMaskFromNative(mask);
-                env->SetIntArrayRegion(jChannelMasks.get(), posMaskIndex++, 1, &jMask);
-            }
-        }
-
-        int encapsulationType;
-        if (audioEncapsulationTypeFromNative(nAudioPort->audio_profiles[i].encapsulation_type,
-                                             &encapsulationType) != NO_ERROR) {
-            ALOGW("Unknown encapsualtion type for JAVA API: %u",
-                  nAudioPort->audio_profiles[i].encapsulation_type);
-            continue;
-        }
-
-        ScopedLocalRef<jobject>
-                jAudioProfile(env,
-                              env->NewObject(gAudioProfileClass, gAudioProfileCstor,
-                                             audioFormatFromNative(
-                                                     nAudioPort->audio_profiles[i].format),
-                                             jSamplingRates.get(), jChannelMasks.get(),
-                                             jChannelIndexMasks.get(), encapsulationType));
-        if (jAudioProfile == nullptr) {
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
-        }
-        env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile.get());
         if (nAudioPort->audio_profiles[i].format == AUDIO_FORMAT_PCM_FLOAT) {
             hasFloat = true;
         } else if (jPcmFloatProfileFromExtendedInteger.get() == nullptr &&
                    audio_is_linear_pcm(nAudioPort->audio_profiles[i].format) &&
                    audio_bytes_per_sample(nAudioPort->audio_profiles[i].format) > 2) {
+            ScopedLocalRef<jintArray>
+                    jSamplingRates(env,
+                                   (jintArray)
+                                           env->GetObjectField(jAudioProfile,
+                                                               gAudioProfileFields.mSamplingRates));
+            ScopedLocalRef<jintArray>
+                    jChannelMasks(env,
+                                  (jintArray)
+                                          env->GetObjectField(jAudioProfile,
+                                                              gAudioProfileFields.mChannelMasks));
+            ScopedLocalRef<jintArray>
+                    jChannelIndexMasks(env,
+                                       (jintArray)env->GetObjectField(jAudioProfile,
+                                                                      gAudioProfileFields
+                                                                              .mChannelIndexMasks));
+            int encapsulationType =
+                    env->GetIntField(jAudioProfile, gAudioProfileFields.mEncapsulationType);
+
             jPcmFloatProfileFromExtendedInteger.reset(
                     env->NewObject(gAudioProfileClass, gAudioProfileCstor,
                                    audioFormatFromNative(AUDIO_FORMAT_PCM_FLOAT),
                                    jSamplingRates.get(), jChannelMasks.get(),
                                    jChannelIndexMasks.get(), encapsulationType));
         }
+
+        if (jAudioProfile != nullptr) {
+            env->DeleteLocalRef(jAudioProfile);
+        }
     }
     if (!hasFloat && jPcmFloatProfileFromExtendedInteger.get() != nullptr) {
         // R and earlier compatibility - add ENCODING_PCM_FLOAT to the end
@@ -3285,6 +3260,14 @@
     jclass audioProfileClass = FindClassOrDie(env, "android/media/AudioProfile");
     gAudioProfileClass = MakeGlobalRefOrDie(env, audioProfileClass);
     gAudioProfileCstor = GetMethodIDOrDie(env, audioProfileClass, "<init>", "(I[I[I[II)V");
+    gAudioProfileFields.mSamplingRates =
+            GetFieldIDOrDie(env, audioProfileClass, "mSamplingRates", "[I");
+    gAudioProfileFields.mChannelMasks =
+            GetFieldIDOrDie(env, audioProfileClass, "mChannelMasks", "[I");
+    gAudioProfileFields.mChannelIndexMasks =
+            GetFieldIDOrDie(env, audioProfileClass, "mChannelIndexMasks", "[I");
+    gAudioProfileFields.mEncapsulationType =
+            GetFieldIDOrDie(env, audioProfileClass, "mEncapsulationType", "I");
 
     jclass audioDescriptorClass = FindClassOrDie(env, "android/media/AudioDescriptor");
     gAudioDescriptorClass = MakeGlobalRefOrDie(env, audioDescriptorClass);
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
deleted file mode 100644
index 9734ab9..0000000
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2011, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "NMST_QTagUidNative"
-
-#include <android/multinetwork.h>
-#include <cutils/qtaguid.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <nativehelper/JNIPlatformHelp.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
-
-#include "jni.h"
-
-namespace android {
-
-static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor,
-                        jint tagNum, jint uid) {
-  int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-
-  if (env->ExceptionCheck()) {
-    ALOGE("Can't get FileDescriptor num");
-    return (jint)-1;
-  }
-
-  int res = android_tag_socket_with_uid(userFd, tagNum, uid);
-  if (res < 0) {
-    return (jint)-errno;
-  }
-  return (jint)res;
-}
-
-static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
-  int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-
-  if (env->ExceptionCheck()) {
-    ALOGE("Can't get FileDescriptor num");
-    return (jint)-1;
-  }
-
-  int res = android_untag_socket(userFd);
-  if (res < 0) {
-    return (jint)-errno;
-  }
-  return (jint)res;
-}
-
-static const JNINativeMethod gQTagUidMethods[] = {
-  { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd},
-  { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd},
-};
-
-int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) {
-  return jniRegisterNativeMethods(env, "com/android/server/NetworkManagementSocketTagger", gQTagUidMethods, NELEM(gQTagUidMethods));
-}
-
-};
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a8cf253..9915913 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -104,6 +104,7 @@
     jfieldID hdrCapabilities;
     jfieldID autoLowLatencyModeSupported;
     jfieldID gameContentTypeSupported;
+    jfieldID preferredBootDisplayMode;
 } gDynamicDisplayInfoClassInfo;
 
 static struct {
@@ -1301,6 +1302,9 @@
 
     env->SetBooleanField(object, gDynamicDisplayInfoClassInfo.gameContentTypeSupported,
                          info.gameContentTypeSupported);
+
+    env->SetIntField(object, gDynamicDisplayInfoClassInfo.preferredBootDisplayMode,
+                     info.preferredBootDisplayMode);
     return object;
 }
 
@@ -1638,6 +1642,27 @@
     }
 }
 
+static jboolean nativeGetBootDisplayModeSupport(JNIEnv* env, jclass clazz) {
+    bool isBootDisplayModeSupported = false;
+    SurfaceComposerClient::getBootDisplayModeSupport(&isBootDisplayModeSupported);
+    return static_cast<jboolean>(isBootDisplayModeSupported);
+}
+
+static void nativeSetBootDisplayMode(JNIEnv* env, jclass clazz, jobject tokenObject,
+                                     jint displayModId) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+    if (token == NULL) return;
+
+    SurfaceComposerClient::setBootDisplayMode(token, displayModId);
+}
+
+static void nativeClearBootDisplayMode(JNIEnv* env, jclass clazz, jobject tokenObject) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+    if (token == NULL) return;
+
+    SurfaceComposerClient::clearBootDisplayMode(token);
+}
+
 static void nativeSetAutoLowLatencyMode(JNIEnv* env, jclass clazz, jobject tokenObject, jboolean on) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
     if (token == NULL) return;
@@ -2046,6 +2071,12 @@
             (void*)nativeGetDisplayNativePrimaries },
     {"nativeSetActiveColorMode", "(Landroid/os/IBinder;I)Z",
             (void*)nativeSetActiveColorMode},
+     {"nativeGetBootDisplayModeSupport", "()Z",
+                (void*)nativeGetBootDisplayModeSupport },
+    {"nativeSetBootDisplayMode", "(Landroid/os/IBinder;I)V",
+            (void*)nativeSetBootDisplayMode },
+    {"nativeClearBootDisplayMode", "(Landroid/os/IBinder;)V",
+            (void*)nativeClearBootDisplayMode },
     {"nativeSetAutoLowLatencyMode", "(Landroid/os/IBinder;Z)V",
             (void*)nativeSetAutoLowLatencyMode },
     {"nativeSetGameContentType", "(Landroid/os/IBinder;Z)V",
@@ -2184,6 +2215,8 @@
             GetFieldIDOrDie(env, dynamicInfoClazz, "autoLowLatencyModeSupported", "Z");
     gDynamicDisplayInfoClassInfo.gameContentTypeSupported =
             GetFieldIDOrDie(env, dynamicInfoClazz, "gameContentTypeSupported", "Z");
+    gDynamicDisplayInfoClassInfo.preferredBootDisplayMode =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "preferredBootDisplayMode", "I");
 
     jclass modeClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayMode");
     gDisplayModeClassInfo.clazz = MakeGlobalRefOrDie(env, modeClazz);
diff --git a/core/proto/android/server/Android.bp b/core/proto/android/server/Android.bp
new file mode 100644
index 0000000..362daa7
--- /dev/null
+++ b/core/proto/android/server/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+  name: "srcs_bluetooth_manager_service_proto",
+  srcs: [
+      "bluetooth_manager_service.proto",
+  ],
+  visibility: ["//packages/modules/Bluetooth:__subpackages__"],
+}
+
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c9bd222..85504ce 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -719,6 +719,7 @@
     <protected-broadcast android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES" />
     <protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" />
     <protected-broadcast android:name="android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER" />
+    <protected-broadcast android:name="android.service.autofill.action.DELAYED_FILL" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -976,61 +977,6 @@
         android:permissionFlags="softRestricted|immutablyRestricted"
         android:protectionLevel="dangerous" />
 
-    <!-- Required to be able to read audio files from shared storage.
-     <p>Protection level: dangerous -->
-    <permission-group android:name="android.permission-group.READ_MEDIA_AURAL"
-                      android:icon="@drawable/perm_group_read_media_aural"
-                      android:label="@string/permgrouplab_readMediaAural"
-                      android:description="@string/permgroupdesc_readMediaAural"
-                      android:priority="950" />
-
-    <!-- Allows an application to read audio files from external storage.
-      <p>This permission is enforced starting in API level
-      {@link android.os.Build.VERSION_CODES#TIRAMISU}.
-      For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
-      targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
-      must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
-     <p>Protection level: dangerous -->
-    <permission android:name="android.permission.READ_MEDIA_AUDIO"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_readMediaAudio"
-                android:description="@string/permdesc_readMediaAudio"
-                android:protectionLevel="dangerous" />
-
-    <!-- Required to be able to read image and video files from shared storage.
-     <p>Protection level: dangerous -->
-    <permission-group android:name="android.permission-group.READ_MEDIA_VISUAL"
-                      android:icon="@drawable/perm_group_read_media_visual"
-                      android:label="@string/permgrouplab_readMediaVisual"
-                      android:description="@string/permgroupdesc_readMediaVisual"
-                      android:priority="1000" />
-
-    <!-- Allows an application to read audio files from external storage.
-    <p>This permission is enforced starting in API level
-    {@link android.os.Build.VERSION_CODES#TIRAMISU}.
-    For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
-    targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
-    must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
-   <p>Protection level: dangerous -->
-    <permission android:name="android.permission.READ_MEDIA_VIDEO"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_readMediaVideo"
-                android:description="@string/permdesc_readMediaVideo"
-                android:protectionLevel="dangerous" />
-
-    <!-- Allows an application to read image files from external storage.
-      <p>This permission is enforced starting in API level
-      {@link android.os.Build.VERSION_CODES#TIRAMISU}.
-      For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
-      targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
-      must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
-     <p>Protection level: dangerous -->
-    <permission android:name="android.permission.READ_MEDIA_IMAGE"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_readMediaImage"
-                android:description="@string/permdesc_readMediaImage"
-                android:protectionLevel="dangerous" />
-
     <!-- Allows an application to write to external storage.
          <p class="note"><strong>Note:</strong> If <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
@@ -1144,6 +1090,15 @@
         android:description="@string/permdesc_accessBackgroundLocation"
         android:protectionLevel="dangerous|instant" />
 
+    <!-- Allows an application (emergency or advanced driver-assistance app) to bypass
+    location settings.
+         <p>Not for use by third-party applications.
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.LOCATION_BYPASS"
+                android:protectionLevel="signature|privileged"/>
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the call log                                 -->
     <!-- ====================================================================== -->
@@ -1647,7 +1602,7 @@
         android:protectionLevel="normal"
         android:permissionFlags="removed"/>
 
-    <!-- @hide We need to keep this around for backwards compatibility -->
+    <!-- @SystemApi @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.WRITE_SMS"
         android:protectionLevel="normal"
         android:permissionFlags="removed"/>
@@ -1725,7 +1680,7 @@
     <permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows an application to monitor incoming Bluetooth MAP messages, to record
+    <!-- @SystemApi Allows an application to monitor incoming Bluetooth MAP messages, to record
          or perform processing on them. -->
     <!-- @hide -->
     <permission android:name="android.permission.RECEIVE_BLUETOOTH_MAP"
@@ -4170,6 +4125,16 @@
     <permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a
+         android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+   -->
+    <permission android:name="android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE"
+        android:protectionLevel="signature" />
+
+
     <!-- Must be declared by a android.service.musicrecognition.MusicRecognitionService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -4672,6 +4637,13 @@
     <permission android:name="android.permission.READ_FRAME_BUFFER"
         android:protectionLevel="signature|recents" />
 
+      <!-- @SystemApi Allows an application to change the touch mode state.
+           Without this permission, an app can only change the touch mode
+           if it currently has focus.
+         @hide -->
+    <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to use InputFlinger's low level features.
          @hide -->
     <permission android:name="android.permission.ACCESS_INPUT_FLINGER"
@@ -5862,6 +5834,13 @@
     <permission android:name="android.permission.MANAGE_SMARTSPACE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manage the wallpaper effects
+     generation service.
+        @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION"
+        android:protectionLevel="signature" />
+
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -5961,6 +5940,10 @@
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
         android:protectionLevel="signature" />
+    <!-- @hide Permission that suppresses the notification when the clipboard is accessed.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION"
+                android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows modifying accessibility state.
          @hide -->
@@ -6164,6 +6147,12 @@
     <permission android:name="android.permission.ACCESS_FPS_COUNTER"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows the GameService provider to create GameSession and call GameSession
+                    APIs and overlay a view on top of the game's Activity.
+     @hide -->
+    <permission android:name="android.permission.MANAGE_GAME_ACTIVITY"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
          when they are performing reboot-blocking work.
          @hide -->
@@ -6290,6 +6279,15 @@
     <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an app to set keep-clear areas without restrictions on the size or
+        number of keep-clear areas (see {@link android.view.View#setPreferKeepClearRects}).
+        When the system arranges floating windows onscreen, it might decide to ignore keep-clear
+        areas from windows, whose owner does not have this permission.
+        @hide
+    -->
+    <permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/anim/activity_close_enter.xml b/core/res/res/anim/activity_close_enter.xml
index 9fa7c54..0fefb51 100644
--- a/core/res/res/anim/activity_close_enter.xml
+++ b/core/res/res/anim/activity_close_enter.xml
@@ -19,16 +19,37 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="false">
-    <scale
-        android:fromXScale="1.1"
-        android:toXScale="1"
-        android:fromYScale="1.1"
-        android:toYScale="1"
-        android:pivotX="50%"
-        android:pivotY="50%"
+
+    <alpha
+        android:fromAlpha="1.0"
+        android:toAlpha="1.0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/linear"
+        android:startOffset="0"
+        android:duration="450" />
+
+    <translate
+        android:fromXDelta="-10%"
+        android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
-        android:duration="400"/>
+        android:startOffset="0"
+        android:duration="450" />
+
+    <extend
+        android:fromExtendLeft="0"
+        android:fromExtendTop="0"
+        android:fromExtendRight="10%"
+        android:fromExtendBottom="0"
+        android:toExtendLeft="0"
+        android:toExtendTop="0"
+        android:toExtendRight="10%"
+        android:toExtendBottom="0"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:startOffset="0"
+        android:duration="450" />
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_close_enter_legacy.xml b/core/res/res/anim/activity_close_enter_legacy.xml
new file mode 100644
index 0000000..9fa7c54
--- /dev/null
+++ b/core/res/res/anim/activity_close_enter_legacy.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <scale
+        android:fromXScale="1.1"
+        android:toXScale="1"
+        android:fromYScale="1.1"
+        android:toYScale="1"
+        android:pivotX="50%"
+        android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="400"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_close_exit.xml b/core/res/res/anim/activity_close_exit.xml
index 1599ae8..f807c26 100644
--- a/core/res/res/anim/activity_close_exit.xml
+++ b/core/res/res/anim/activity_close_exit.xml
@@ -18,27 +18,38 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false"
-    android:zAdjustment="top">
+    android:shareInterpolator="false">
+
     <alpha
-        android:fromAlpha="1"
+        android:fromAlpha="1.0"
         android:toAlpha="0.0"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
         android:interpolator="@interpolator/linear"
-        android:startOffset="33"
-        android:duration="50"/>
-    <scale
-        android:fromXScale="1"
-        android:toXScale="0.9"
-        android:fromYScale="1"
-        android:toYScale="0.9"
-        android:pivotX="50%"
-        android:pivotY="50%"
+        android:startOffset="35"
+        android:duration="83" />
+
+    <translate
+        android:fromXDelta="0"
+        android:toXDelta="10%"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
-        android:duration="400"/>
+        android:startOffset="0"
+        android:duration="450" />
+
+    <extend
+        android:fromExtendLeft="10%"
+        android:fromExtendTop="0"
+        android:fromExtendRight="0"
+        android:fromExtendBottom="0"
+        android:toExtendLeft="10%"
+        android:toExtendTop="0"
+        android:toExtendRight="0"
+        android:toExtendBottom="0"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:startOffset="0"
+        android:duration="450" />
 </set>
diff --git a/core/res/res/anim/activity_close_exit_legacy.xml b/core/res/res/anim/activity_close_exit_legacy.xml
new file mode 100644
index 0000000..1599ae8
--- /dev/null
+++ b/core/res/res/anim/activity_close_exit_legacy.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false"
+    android:zAdjustment="top">
+    <alpha
+        android:fromAlpha="1"
+        android:toAlpha="0.0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/linear"
+        android:startOffset="33"
+        android:duration="50"/>
+    <scale
+        android:fromXScale="1"
+        android:toXScale="0.9"
+        android:fromYScale="1"
+        android:toYScale="0.9"
+        android:pivotX="50%"
+        android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="400"/>
+</set>
diff --git a/core/res/res/anim/activity_open_enter.xml b/core/res/res/anim/activity_open_enter.xml
index 38d3e8ed..1674dab 100644
--- a/core/res/res/anim/activity_open_enter.xml
+++ b/core/res/res/anim/activity_open_enter.xml
@@ -18,6 +18,7 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="false">
+
     <alpha
         android:fromAlpha="0"
         android:toAlpha="1.0"
@@ -26,17 +27,27 @@
         android:fillAfter="true"
         android:interpolator="@interpolator/linear"
         android:startOffset="50"
-        android:duration="50"/>
-    <scale
-        android:fromXScale="0.85"
-        android:toXScale="1"
-        android:fromYScale="0.85"
-        android:toYScale="1"
-        android:pivotX="50%"
-        android:pivotY="50%"
+        android:duration="83" />
+
+    <translate
+        android:fromXDelta="10%"
+        android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
-        android:duration="400"/>
+        android:duration="450" />
+
+    <extend
+        android:fromExtendLeft="10%"
+        android:fromExtendTop="0"
+        android:fromExtendRight="0"
+        android:fromExtendBottom="0"
+        android:toExtendLeft="10%"
+        android:toExtendTop="0"
+        android:toExtendRight="0"
+        android:toExtendBottom="0"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:startOffset="0"
+        android:duration="450" />
 </set>
diff --git a/core/res/res/anim/activity_open_enter_legacy.xml b/core/res/res/anim/activity_open_enter_legacy.xml
new file mode 100644
index 0000000..38d3e8ed
--- /dev/null
+++ b/core/res/res/anim/activity_open_enter_legacy.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <alpha
+        android:fromAlpha="0"
+        android:toAlpha="1.0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/linear"
+        android:startOffset="50"
+        android:duration="50"/>
+    <scale
+        android:fromXScale="0.85"
+        android:toXScale="1"
+        android:fromYScale="0.85"
+        android:toYScale="1"
+        android:pivotX="50%"
+        android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="400"/>
+</set>
diff --git a/core/res/res/anim/activity_open_exit.xml b/core/res/res/anim/activity_open_exit.xml
index 3865d21..372f2c8 100644
--- a/core/res/res/anim/activity_open_exit.xml
+++ b/core/res/res/anim/activity_open_exit.xml
@@ -19,27 +19,36 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="false">
 
-    <!-- Fade out, over a black surface, which simulates a black scrim -->
     <alpha
-        android:fromAlpha="1"
-        android:toAlpha="0.4"
+        android:fromAlpha="1.0"
+        android:toAlpha="1.0"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
-        android:interpolator="@interpolator/linear"
-        android:startOffset="83"
-        android:duration="167"/>
+        android:interpolator="@interpolator/standard_accelerate"
+        android:startOffset="0"
+        android:duration="450" />
 
-    <scale
-        android:fromXScale="1"
-        android:toXScale="1.05"
-        android:fromYScale="1"
-        android:toYScale="1.05"
-        android:pivotX="50%"
-        android:pivotY="50%"
+    <translate
+        android:fromXDelta="0"
+        android:toXDelta="-10%"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
-        android:duration="400"/>
+        android:startOffset="0"
+        android:duration="450" />
+
+    <extend
+        android:fromExtendLeft="0"
+        android:fromExtendTop="0"
+        android:fromExtendRight="10%"
+        android:fromExtendBottom="0"
+        android:toExtendLeft="0"
+        android:toExtendTop="0"
+        android:toExtendRight="10%"
+        android:toExtendBottom="0"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:startOffset="0"
+        android:duration="450" />
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_open_exit_legacy.xml b/core/res/res/anim/activity_open_exit_legacy.xml
new file mode 100644
index 0000000..3865d21
--- /dev/null
+++ b/core/res/res/anim/activity_open_exit_legacy.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+
+    <!-- Fade out, over a black surface, which simulates a black scrim -->
+    <alpha
+        android:fromAlpha="1"
+        android:toAlpha="0.4"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/linear"
+        android:startOffset="83"
+        android:duration="167"/>
+
+    <scale
+        android:fromXScale="1"
+        android:toXScale="1.05"
+        android:fromYScale="1"
+        android:toYScale="1.05"
+        android:pivotX="50%"
+        android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="400"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/drawable-car/car_checkbox.xml b/core/res/res/drawable-car/car_checkbox.xml
deleted file mode 100644
index 083a7aa..0000000
--- a/core/res/res/drawable-car/car_checkbox.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright 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.
--->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:width="@*android:dimen/car_primary_icon_size"
-        android:height="@*android:dimen/car_primary_icon_size"
-        android:drawable="@drawable/btn_check_material_anim"/>
-    <item
-        android:width="@*android:dimen/car_primary_icon_size"
-        android:height="@*android:dimen/car_primary_icon_size"
-        android:drawable="@drawable/car_checkbox_background"/>
-</layer-list>
diff --git a/core/res/res/drawable-car/car_checkbox_background.xml b/core/res/res/drawable-car/car_checkbox_background.xml
deleted file mode 100644
index 69dcdbb..0000000
--- a/core/res/res/drawable-car/car_checkbox_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright 2021 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" android:state_pressed="true">
-        <shape android:shape="rectangle">
-            <solid android:color="#8A0041BE" />
-            <stroke android:width="4dp" android:color="#0041BE" />
-        </shape>
-    </item>
-    <item android:state_focused="true">
-        <shape android:shape="rectangle">
-            <solid android:color="#3D0059B3" />
-            <stroke android:width="8dp" android:color="#0059B3" />
-        </shape>
-    </item>
-</selector>
diff --git a/core/res/res/drawable/perm_group_read_media_aural.xml b/core/res/res/drawable/perm_group_read_media_aural.xml
deleted file mode 100644
index 6fc9c69..0000000
--- a/core/res/res/drawable/perm_group_read_media_aural.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2015 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M10,21q-1.65,0 -2.825,-1.175Q6,18.65 6,17q0,-1.65 1.175,-2.825Q8.35,13 10,13q0.575,0 1.063,0.137 0.487,0.138 0.937,0.413V3h6v4h-4v10q0,1.65 -1.175,2.825Q11.65,21 10,21z"/>
-</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/perm_group_read_media_visual.xml b/core/res/res/drawable/perm_group_read_media_visual.xml
deleted file mode 100644
index a5db271..0000000
--- a/core/res/res/drawable/perm_group_read_media_visual.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2015 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
-</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
new file mode 100644
index 0000000..44ed6f2
--- /dev/null
+++ b/core/res/res/layout/miniresolver.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.internal.widget.ResolverDrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:maxWidth="@dimen/resolver_max_width"
+    android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height"
+    android:maxCollapsedHeightSmall="56dp"
+    android:id="@id/contentPanel">
+
+    <RelativeLayout
+        android:id="@+id/title_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alwaysShow="true"
+        android:elevation="@dimen/resolver_elevation"
+        android:paddingTop="@dimen/resolver_small_margin"
+        android:paddingStart="@dimen/resolver_edge_margin"
+        android:paddingEnd="@dimen/resolver_edge_margin"
+        android:paddingBottom="@dimen/resolver_title_padding_bottom"
+        android:background="@drawable/bottomsheet_background">
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true"
+        />
+
+        <TextView
+            android:id="@+id/open_cross_profile"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/icon"
+            android:layout_centerHorizontal="true"
+            android:textColor="?android:textColorPrimary"
+        />
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/button_bar_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alwaysShow="true"
+        android:orientation="vertical"
+        android:background="?attr/colorBackground"
+        android:layout_ignoreOffset="true">
+        <View
+            android:id="@+id/resolver_button_bar_divider"
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="?attr/colorBackground"
+            android:foreground="?attr/dividerVertical" />
+        <RelativeLayout
+            style="?attr/buttonBarStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_ignoreOffset="true"
+            android:layout_hasNestedScrollIndicator="true"
+            android:gravity="end|center_vertical"
+            android:orientation="horizontal"
+            android:layoutDirection="locale"
+            android:measureWithLargestChild="true"
+            android:paddingTop="@dimen/resolver_button_bar_spacing"
+            android:paddingBottom="@dimen/resolver_button_bar_spacing"
+            android:paddingStart="@dimen/resolver_edge_margin"
+            android:paddingEnd="@dimen/resolver_small_margin"
+            android:elevation="@dimen/resolver_elevation">
+
+            <Button
+                android:id="@+id/use_same_profile_browser"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:maxLines="2"
+                style="@android:style/Widget.DeviceDefault.Button.Borderless"
+                android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+                android:textAllCaps="false"
+                android:text="@string/activity_resolver_use_once"
+            />
+
+            <Button
+                android:id="@+id/button_open"
+                android:layout_width="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:maxLines="2"
+                style="@android:style/Widget.DeviceDefault.Button.Colored"
+                android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+                android:textAllCaps="false"
+                android:layout_height="wrap_content"
+                android:text="@string/whichViewApplicationLabel"
+            />
+        </RelativeLayout>
+    </LinearLayout>
+</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7916ef4..afe0f1b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2885,7 +2885,7 @@
              <code>public void sayHello(View v)</code> method of your context
              (typically, your Activity).
              {@deprecated View actually traverses the Context
-             hierarchy looking for the relevant method, which is fragile (an intermediate 
+             hierarchy looking for the relevant method, which is fragile (an intermediate
              ContextWrapper adding a same-named method would change behavior) and restricts
              bytecode optimizers such as R8. Instead, use View.setOnClickListener.}-->
         <attr name="onClick" format="string" />
@@ -6975,6 +6975,34 @@
         <attr name="toBottom" format="fraction" />
     </declare-styleable>
 
+    <!-- Defines the ExtendAnimation used to extend windows during animations -->
+    <declare-styleable name="ExtendAnimation">
+        <!-- Defines the amount a window should be extended outward from the left at
+             the start of the animation. -->
+        <attr name="fromExtendLeft" format="float|fraction" />
+        <!-- Defines the amount a window should be extended outward from the top at
+             the start of the animation. -->
+        <attr name="fromExtendTop" format="float|fraction" />
+        <!-- Defines the amount a window should be extended outward from the right at
+             the start of the animation. -->
+        <attr name="fromExtendRight" format="float|fraction" />
+        <!-- Defines the amount a window should be extended outward from the bottom at
+             the start of the animation. -->
+        <attr name="fromExtendBottom" format="float|fraction" />
+        <!-- Defines the amount a window should be extended outward from the left by
+             the end of the animation by transitioning from the fromExtendLeft amount. -->
+        <attr name="toExtendLeft" format="float|fraction" />
+        <!-- Defines the amount a window should be extended outward from the top by
+             the end of the animation by transitioning from the fromExtendTop amount. -->
+        <attr name="toExtendTop" format="float|fraction" />
+        <!-- Defines the amount a window should be extended outward from the right by
+             the end of the animation by transitioning from the fromExtendRight amount. -->
+        <attr name="toExtendRight" format="float|fraction" />
+        <!-- Defines the amount a window should be extended outward from the bottom by
+             the end of the animation by transitioning from the fromExtendBottom amount. -->
+        <attr name="toExtendBottom" format="float|fraction" />
+    </declare-styleable>
+
     <declare-styleable name="LayoutAnimation">
         <!-- Fraction of the animation duration used to delay the beginning of
          the animation of each child. -->
@@ -7827,7 +7855,7 @@
 
         <!-- Name of a method on the Context used to inflate the menu that will be
              called when the item is clicked.
-             {@deprecated Menu actually traverses the Context hierarchy looking for the 
+             {@deprecated Menu actually traverses the Context hierarchy looking for the
              relevant method, which is fragile (an intermediate ContextWrapper adding a
              same-named method would change behavior) and restricts bytecode optimizers
              such as R8. Instead, use MenuItem.setOnMenuItemClickListener.} -->
@@ -8847,6 +8875,22 @@
         <attr name="gameSessionService" format="string" />
     </declare-styleable>
 
+    <!-- Use <code>game-mode-config</code> as the root tag of the XML resource that
+         describes a GameModeConfig.
+         Described here are the attributes that can be included in that tag. -->
+    <declare-styleable name="GameModeConfig">
+        <!-- Set true to opt in BATTERY mode. -->
+        <attr name="supportsBatteryGameMode" format="boolean" />
+        <!-- Set true to opt in PERFORMANCE mode. -->
+        <attr name="supportsPerformanceGameMode" format="boolean" />
+        <!-- Set true to enable ANGLE. -->
+        <attr name="allowGameAngleDriver" format="boolean" />
+        <!-- Set true to allow resolution downscaling intervention. -->
+        <attr name="allowGameDownscaling" format="boolean" />
+        <!-- Set true to allow FPS override intervention. -->
+        <attr name="allowGameFpsOverride" format="boolean" />
+    </declare-styleable>
+
     <!-- Use <code>voice-enrollment-application</code>
          as the root tag of the XML resource that escribes the supported keyphrases (hotwords)
          by the enrollment application.
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 3a2fb6e..cb40e86 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2839,6 +2839,14 @@
         <attr name="path" />
         <attr name="minSdkVersion" />
         <attr name="maxSdkVersion" />
+        <!-- The order in which the apex system services are initiated. When there are dependencies
+        among apex system services, setting this attribute for each of them ensures that they are
+        created in the order required by those dependencies. The apex-system-services that are
+        started manually within SystemServer ignore the initOrder and are not considered for
+        automatic starting of the other services.
+        The value is a simple integer, with higher number being initialized first. If not specified,
+        the default order is 0. -->
+        <attr name="initOrder" format="integer" />
     </declare-styleable>
 
     <!-- The <code>receiver</code> tag declares an
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 78841fc..53cf463 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2376,6 +2376,12 @@
     <!-- ComponentNames of the dreams that we should hide -->
     <string-array name="config_disabledDreamComponents" translatable="false">
     </string-array>
+    <!-- The list of supported dream complications -->
+    <integer-array name="config_supportedDreamComplications">
+    </integer-array>
+    <!-- The list of dream complications which should be enabled by default -->
+    <integer-array name="config_dreamComplicationsEnabledByDefault">
+    </integer-array>
 
     <!-- Are we allowed to dream while not plugged in? -->
     <bool name="config_dreamsEnabledOnBattery">false</bool>
@@ -2612,6 +2618,10 @@
          will be locked. -->
     <bool name="config_multiuserDelayUserDataLocking">false</bool>
 
+    <!-- Whether to automatically switch a non-primary user back to the primary user after a
+         timeout when the device is docked.  -->
+    <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+
     <!-- Whether to only install system packages on a user if they're allowlisted for that user
          type. These are flags and can be freely combined.
          0  - disable allowlist (install all system packages; no logging)
@@ -2707,6 +2717,9 @@
          Values are bandwidth_estimator, carrier_config and modem. -->
     <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
 
+    <!-- Whether force to enable telephony new data stack or not -->
+    <bool name="config_force_enable_telephony_new_data_stack">false</bool>
+
     <!-- Whether WiFi display is supported by this device.
          There are many prerequisites for this feature to work correctly.
          Here are a few of them:
@@ -2884,6 +2897,11 @@
     <string name="config_sensorUseStartedActivity" translatable="false"
             >com.android.systemui/com.android.systemui.sensorprivacy.SensorUseStartedActivity</string>
 
+    <!-- Component name of the activity used to ask a user to confirm system language change after
+         receiving <Set Menu Language> CEC message. -->
+    <string name="config_hdmiCecSetMenuLanguageActivity"
+            >com.android.systemui/com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity</string>
+
     <!-- Name of the dialog that is used to request the user's consent for a Platform VPN -->
     <string name="config_platformVpnConfirmDialogComponent" translatable="false"
             >com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string>
@@ -4161,6 +4179,15 @@
 
     <string name="config_defaultMusicRecognitionService" translatable="false"></string>
 
+    <!-- The package name for the system's wallpaper effects generation service.
+    This service returns wallpaper effects results.
+    This service must be trusted, as it can be activated without explicit consent of the user.
+    If no service with the specified name exists on the device, wallpaper effects
+    generation service will be disabled.
+    Example: "com.android.intelligence/.WallpaperEffectsGenerationService"
+-->
+    <string name="config_defaultWallpaperEffectsGenerationService" translatable="false"></string>
+
     <!-- The package name for the default retail demo app.
          This package must be trusted, as it has the permissions to query the usage stats on the
          device.
@@ -4971,6 +4998,10 @@
     <!-- URI used for Nearby Share SliceProvider scanning. -->
     <string translatable="false" name="config_defaultNearbySharingSliceUri"></string>
 
+    <!-- Component name that accepts settings intents for saved devices.
+             Used by FastPairSettingsFragment. -->
+    <string translatable="false" name="config_defaultNearbyFastPairSettingsDevicesComponent"></string>
+
     <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
          check after reboot or airplane mode toggling -->
     <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool>
@@ -5133,6 +5164,9 @@
         If given value is outside of this range, the option 1 (center) is assummed. -->
     <integer name="config_letterboxDefaultPositionForReachability">1</integer>
 
+    <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
+    <bool name="config_letterboxIsEducationEnabled">false</bool>
+
     <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
          treatment for stretched issues in camera viewfinder. -->
     <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
@@ -5248,6 +5282,12 @@
     <bool name="config_cecTvSendStandbyOnSleepDisabled_allowed">true</bool>
     <bool name="config_cecTvSendStandbyOnSleepDisabled_default">false</bool>
 
+    <bool name="config_cecSetMenuLanguage_userConfigurable">true</bool>
+    <bool name="config_cecSetMenuLanguageEnabled_allowed">true</bool>
+    <bool name="config_cecSetMenuLanguageEnabled_default">true</bool>
+    <bool name="config_cecSetMenuLanguageDisabled_allowed">true</bool>
+    <bool name="config_cecSetMenuLanguageDisabled_default">false</bool>
+
     <bool name="config_cecRcProfileTv_userConfigurable">true</bool>
     <bool name="config_cecRcProfileTvNone_allowed">true</bool>
     <bool name="config_cecRcProfileTvNone_default">true</bool>
@@ -5634,4 +5674,13 @@
     <!-- The amount of time after becoming non-interactive (in ms) after which
          Low Power Standby can activate. -->
     <integer name="config_lowPowerStandbyNonInteractiveTimeout">5000</integer>
+
+
+    <!-- Mapping to select an Intent.EXTRA_DOCK_STATE value from extcon state
+         key-value pairs. Each entry is evaluated in order and is of the form:
+            "[EXTRA_DOCK_STATE value],key1=value1,key2=value2[,...]"
+         An entry with no key-value pairs is valid and can be used as a wildcard.
+     -->
+    <string-array name="config_dockExtconStateMapping">
+    </string-array>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2e96c65..d57f5ba 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3255,11 +3255,24 @@
     <public name="showClockAndComplications" />
     <!-- @hide @SystemApi -->
     <public name="gameSessionService" />
+    <public name="supportsBatteryGameMode" />
+    <public name="supportsPerformanceGameMode" />
+    <public name="allowGameAngleDriver" />
+    <public name="allowGameDownscaling" />
+    <public name="allowGameFpsOverride" />
     <public name="localeConfig" />
     <public name="showBackground" />
     <public name="inheritKeyStoreKeys" />
     <public name="preferKeepClear" />
     <public name="autoHandwritingEnabled" />
+    <public name="fromExtendLeft" />
+    <public name="fromExtendTop" />
+    <public name="fromExtendRight" />
+    <public name="fromExtendBottom" />
+    <public name="toExtendLeft" />
+    <public name="toExtendTop" />
+    <public name="toExtendRight" />
+    <public name="toExtendBottom" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 49a12d1..52c6205 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -880,16 +880,6 @@
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgroupdesc_storage">access photos, media, and files on your device</string>
 
-    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
-    <string name="permgrouplab_readMediaAural">Music &amp; other audio</string>
-    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
-    <string name="permgroupdesc_readMediaAural">access audio files on your device</string>
-
-    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
-    <string name="permgrouplab_readMediaVisual">Photos &amp; videos</string>
-    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
-    <string name="permgroupdesc_readMediaVisual">access images and video files on your device</string>
-
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_microphone">Microphone</string>
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1903,21 +1893,6 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
     <string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string>
 
-    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permlab_readMediaAudio">read audio files from shared storage</string>
-    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permdesc_readMediaAudio">Allows the app to read audio files from your shared storage.</string>
-
-    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permlab_readMediaVideo">read video files from shared storage</string>
-    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permdesc_readMediaVideo">Allows the app to read video files from your shared storage.</string>
-
-    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permlab_readMediaImage">read image files from shared storage</string>
-    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
-    <string name="permdesc_readMediaImage">Allows the app to read image files from your shared storage.</string>
-
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
     <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
@@ -5882,6 +5857,8 @@
     <string name="accessibility_system_action_lock_screen_label">Lock Screen</string>
     <!-- Label for taking screenshot action [CHAR LIMIT=NONE] -->
     <string name="accessibility_system_action_screenshot_label">Screenshot</string>
+    <!-- Label for headset hook action [CHAR LIMIT=NONE] -->
+    <string name="accessibility_system_action_headset_hook_label">Headset Hook</string>
     <!-- Label for triggering on-screen accessibility shortcut action [CHAR LIMIT=NONE] -->
     <string name="accessibility_system_action_on_screen_a11y_shortcut_label">On-screen Accessibility Shortcut</string>
     <!-- Label for showing on-screen accessibility shortcut chooser action [CHAR LIMIT=NONE] -->
@@ -5890,6 +5867,16 @@
     <string name="accessibility_system_action_hardware_a11y_shortcut_label">Accessibility Shortcut</string>
     <!-- Label for dismissing the notification shade [CHAR LIMIT=NONE] -->
     <string name="accessibility_system_action_dismiss_notification_shade">Dismiss Notification Shade</string>
+    <!-- Label for Dpad up action [CHAR LIMIT=NONE] -->
+    <string name="accessibility_system_action_dpad_up_label">Dpad Up</string>
+    <!-- Label for Dpad down action [CHAR LIMIT=NONE] -->
+    <string name="accessibility_system_action_dpad_down_label">Dpad Down</string>
+    <!-- Label for Dpad left action [CHAR LIMIT=NONE] -->
+    <string name="accessibility_system_action_dpad_left_label">Dpad Left</string>
+    <!-- Label for Dpad right action [CHAR LIMIT=NONE] -->
+    <string name="accessibility_system_action_dpad_right_label">Dpad Right</string>
+    <!-- Label for Dpad center action [CHAR LIMIT=NONE] -->
+    <string name="accessibility_system_action_dpad_center_label">Dpad Center</string>
     <!-- Accessibility description of caption view -->
     <string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
 
@@ -5949,9 +5936,9 @@
     <string name="resolver_no_personal_apps_available">No personal apps</string>
 
     <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
-    <string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string>
+    <string name="miniresolver_open_in_personal">Open <xliff:g id="app" example="YouTube">%s</xliff:g> in your personal profile?</string>
     <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
-    <string name="miniresolver_open_in_work">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in work profile?</string>
+    <string name="miniresolver_open_in_work">Open <xliff:g id="app" example="YouTube">%s</xliff:g> in your work profile?</string>
     <!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] -->
     <string name="miniresolver_use_personal_browser">Use personal browser</string>
     <!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 20c5c61..facfdb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -375,6 +375,7 @@
   <java-symbol type="string" name="config_usbConfirmActivity" />
   <java-symbol type="string" name="config_usbResolverActivity" />
   <java-symbol type="string" name="config_sensorUseStartedActivity" />
+  <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
@@ -465,6 +466,7 @@
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
+  <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
   <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="xml" name="config_user_types" />
   <java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -472,6 +474,7 @@
   <java-symbol type="integer" name="config_mobile_mtu" />
   <java-symbol type="array"   name="config_mobile_tcp_buffers" />
   <java-symbol type="string"  name="config_tcp_buffers" />
+  <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
   <java-symbol type="integer" name="config_volte_replacement_rat"/>
   <java-symbol type="integer" name="config_valid_wappush_index" />
   <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -1596,6 +1599,13 @@
   <java-symbol type="layout" name="resolver_list_per_profile" />
   <java-symbol type="layout" name="chooser_list_per_profile" />
   <java-symbol type="layout" name="resolver_empty_states" />
+  <java-symbol type="id" name="open_cross_profile" />
+  <java-symbol type="string" name="miniresolver_open_in_personal" />
+  <java-symbol type="string" name="miniresolver_open_in_work" />
+  <java-symbol type="string" name="miniresolver_use_personal_browser" />
+  <java-symbol type="string" name="miniresolver_use_work_browser" />
+  <java-symbol type="id" name="button_open" />
+  <java-symbol type="id" name="use_same_profile_browser" />
 
   <java-symbol type="anim" name="slide_in_child_bottom" />
   <java-symbol type="anim" name="slide_in_right" />
@@ -1694,7 +1704,13 @@
   <java-symbol type="anim" name="activity_translucent_open_enter" />
   <java-symbol type="anim" name="activity_translucent_close_exit" />
   <java-symbol type="anim" name="activity_open_enter" />
+  <java-symbol type="anim" name="activity_open_exit" />
+  <java-symbol type="anim" name="activity_close_enter" />
   <java-symbol type="anim" name="activity_close_exit" />
+  <java-symbol type="anim" name="activity_open_enter_legacy" />
+  <java-symbol type="anim" name="activity_open_exit_legacy" />
+  <java-symbol type="anim" name="activity_close_enter_legacy" />
+  <java-symbol type="anim" name="activity_close_exit_legacy" />
   <java-symbol type="anim" name="task_fragment_close_enter" />
   <java-symbol type="anim" name="task_fragment_close_exit" />
   <java-symbol type="anim" name="task_fragment_open_enter" />
@@ -2212,6 +2228,8 @@
   <java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" />
   <java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
   <java-symbol type="string" name="config_dreamsDefaultComponent" />
+  <java-symbol type="array" name="config_supportedDreamComplications" />
+  <java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" />
   <java-symbol type="drawable" name="default_dream_preview" />
   <java-symbol type="array" name="config_disabledDreamComponents" />
   <java-symbol type="string" name="config_dozeComponent" />
@@ -2704,6 +2722,7 @@
   <java-symbol type="bool" name="config_allow_ussd_over_ims" />
   <java-symbol type="attr" name="touchscreenBlocksFocus" />
   <java-symbol type="layout" name="resolver_list_with_default" />
+  <java-symbol type="layout" name="miniresolver" />
   <java-symbol type="string" name="activity_resolver_use_always" />
   <java-symbol type="string" name="whichApplicationNamed" />
   <java-symbol type="string" name="whichApplicationLabel" />
@@ -3670,6 +3689,7 @@
   <java-symbol type="string" name="config_defaultContentSuggestionsService" />
   <java-symbol type="string" name="config_defaultSearchUiService" />
   <java-symbol type="string" name="config_defaultSmartspaceService" />
+  <java-symbol type="string" name="config_defaultWallpaperEffectsGenerationService" />
   <java-symbol type="string" name="config_defaultMusicRecognitionService" />
   <java-symbol type="string" name="config_defaultAttentionService" />
   <java-symbol type="string" name="config_defaultRotationResolverService" />
@@ -4098,6 +4118,7 @@
   <java-symbol type="layout" name="chooser_action_button" />
   <java-symbol type="dimen" name="chooser_action_button_icon_size" />
   <java-symbol type="string" name="config_defaultNearbySharingComponent" />
+  <java-symbol type="string" name="config_defaultNearbyFastPairSettingsDevicesComponent" />
   <java-symbol type="bool" name="config_disable_all_cb_messages" />
   <java-symbol type="drawable" name="ic_close" />
 
@@ -4125,10 +4146,16 @@
   <java-symbol type="string" name="accessibility_system_action_quick_settings_label" />
   <java-symbol type="string" name="accessibility_system_action_recents_label" />
   <java-symbol type="string" name="accessibility_system_action_screenshot_label" />
+  <java-symbol type="string" name="accessibility_system_action_headset_hook_label" />
   <java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_label" />
   <java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" />
   <java-symbol type="string" name="accessibility_system_action_hardware_a11y_shortcut_label" />
   <java-symbol type="string" name="accessibility_system_action_dismiss_notification_shade" />
+  <java-symbol type="string" name="accessibility_system_action_dpad_up_label" />
+  <java-symbol type="string" name="accessibility_system_action_dpad_down_label" />
+  <java-symbol type="string" name="accessibility_system_action_dpad_left_label" />
+  <java-symbol type="string" name="accessibility_system_action_dpad_right_label" />
+  <java-symbol type="string" name="accessibility_system_action_dpad_center_label" />
 
   <java-symbol type="string" name="accessibility_freeform_caption" />
 
@@ -4328,6 +4355,7 @@
   <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
   <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
+  <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
   <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
@@ -4448,6 +4476,12 @@
   <java-symbol type="bool" name="config_cecTvSendStandbyOnSleepDisabled_allowed" />
   <java-symbol type="bool" name="config_cecTvSendStandbyOnSleepDisabled_default" />
 
+  <java-symbol type="bool" name="config_cecSetMenuLanguage_userConfigurable" />
+  <java-symbol type="bool" name="config_cecSetMenuLanguageEnabled_allowed" />
+  <java-symbol type="bool" name="config_cecSetMenuLanguageEnabled_default" />
+  <java-symbol type="bool" name="config_cecSetMenuLanguageDisabled_allowed" />
+  <java-symbol type="bool" name="config_cecSetMenuLanguageDisabled_default" />
+
   <java-symbol type="bool" name="config_cecRcProfileTv_userConfigurable" />
   <java-symbol type="bool" name="config_cecRcProfileTvNone_allowed" />
   <java-symbol type="bool" name="config_cecRcProfileTvNone_default" />
@@ -4675,6 +4709,8 @@
 
   <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
 
+  <java-symbol type="array" name="config_dockExtconStateMapping" />
+
   <java-symbol type="string" name="notification_channel_abusive_bg_apps"/>
   <java-symbol type="string" name="notification_title_abusive_bg_apps"/>
   <java-symbol type="string" name="notification_content_abusive_bg_apps"/>
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout.xml b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
new file mode 100644
index 0000000..713a4c8
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
new file mode 100644
index 0000000..74c939b
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/themed_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:theme="@style/RelativeLayoutAlignTop25Alpha"/>
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to core/tests/coretests/res/layout/remote_views_light_background_text.xml
index dd818a0..f300f09 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
+  ~ Copyright (C) 2016 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -12,11 +12,10 @@
   ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT 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
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <gradient
-        android:angle="90"
-        android:startColor="@color/screenshot_background_protection_start"
-        android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/light_background_text"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/core/tests/coretests/res/layout/remote_views_list.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to core/tests/coretests/res/layout/remote_views_list.xml
index dd818a0..ca43bc8 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/core/tests/coretests/res/layout/remote_views_list.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
+  ~ Copyright (C) 2016 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -12,11 +12,10 @@
   ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT 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
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <gradient
-        android:angle="90"
-        android:startColor="@color/screenshot_background_protection_start"
-        android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
+
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 352b4dc..32eebb35 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -34,6 +34,16 @@
     <style name="LayoutInDisplayCutoutModeAlways">
         <item name="android:windowLayoutInDisplayCutoutMode">always</item>
     </style>
+    <style name="RelativeLayoutAlignBottom50Alpha">
+        <item name="android:layout_alignParentTop">false</item>
+        <item name="android:layout_alignParentBottom">true</item>
+        <item name="android:alpha">0.5</item>
+    </style>
+    <style name="RelativeLayoutAlignTop25Alpha">
+        <item name="android:layout_alignParentTop">true</item>
+        <item name="android:layout_alignParentBottom">false</item>
+        <item name="android:alpha">0.25</item>
+    </style>
     <style name="WindowBackgroundColorLiteral">
         <item name="android:windowBackground">#00FF00</item>
     </style>
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index d3e8bb0..5338d04 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -37,9 +37,9 @@
 @SmallTest
 public class PropertyInvalidatedCacheTests {
 
-    // This property is never set.  The test process does not have permission to set any
-    // properties.
-    static final String CACHE_PROPERTY = "cache_key.cache_test_a";
+    // Configuration for creating caches
+    private static final int MODULE = PropertyInvalidatedCache.MODULE_TEST;
+    private static final String API = "testApi";
 
     // This class is a proxy for binder calls.  It contains a counter that increments
     // every time the class is queried.
@@ -64,6 +64,25 @@
         }
     }
 
+    // The functions for querying the server.
+    private static class ServerQuery
+            extends PropertyInvalidatedCache.QueryHandler<Integer, Boolean> {
+        private final ServerProxy mServer;
+
+        ServerQuery(ServerProxy server) {
+            mServer = server;
+        }
+
+        @Override
+        public Boolean apply(Integer x) {
+            return mServer.query(x);
+        }
+        @Override
+        public boolean shouldBypassCache(Integer x) {
+            return x % 13 == 0;
+        }
+    }
+
     // Clear the test mode after every test, in case this process is used for other
     // tests. This also resets the test property map.
     @After
@@ -82,19 +101,11 @@
 
         // Create a cache that uses simple arithmetic to computer its values.
         PropertyInvalidatedCache<Integer, Boolean> testCache =
-                new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
-                    @Override
-                    public Boolean recompute(Integer x) {
-                        return tester.query(x);
-                    }
-                    @Override
-                    public boolean bypass(Integer x) {
-                        return x % 13 == 0;
-                    }
-                };
+                new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+                        new ServerQuery(tester));
 
         PropertyInvalidatedCache.setTestMode(true);
-        PropertyInvalidatedCache.testPropertyName(CACHE_PROPERTY);
+        testCache.testPropertyName();
 
         tester.verify(0);
         assertEquals(tester.value(3), testCache.query(3));
@@ -136,26 +147,14 @@
 
         // Three caches, all using the same system property but one uses a different name.
         PropertyInvalidatedCache<Integer, Boolean> cache1 =
-                new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
-                    @Override
-                    public Boolean recompute(Integer x) {
-                        return tester.query(x);
-                    }
-                };
+            new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+                        new ServerQuery(tester));
         PropertyInvalidatedCache<Integer, Boolean> cache2 =
-                new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
-                    @Override
-                    public Boolean recompute(Integer x) {
-                        return tester.query(x);
-                    }
-                };
+            new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+                        new ServerQuery(tester));
         PropertyInvalidatedCache<Integer, Boolean> cache3 =
-                new PropertyInvalidatedCache<>(4, CACHE_PROPERTY, "cache3") {
-                    @Override
-                    public Boolean recompute(Integer x) {
-                        return tester.query(x);
-                    }
-                };
+            new PropertyInvalidatedCache<>(4, MODULE, API, "cache3",
+                        new ServerQuery(tester));
 
         // Caches are enabled upon creation.
         assertEquals(false, cache1.getDisabledState());
@@ -176,45 +175,29 @@
         assertEquals(false, cache3.getDisabledState());
 
         // Create a new cache1.  Verify that the new instance is disabled.
-        cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
-                    @Override
-                    public Boolean recompute(Integer x) {
-                        return tester.query(x);
-                    }
-                };
+        cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+                new ServerQuery(tester));
         assertEquals(true, cache1.getDisabledState());
 
         // Remove the record of caches being locally disabled.  This is a clean-up step.
-        cache1.clearDisableLocal();
+        cache1.forgetDisableLocal();
         assertEquals(true, cache1.getDisabledState());
         assertEquals(true, cache2.getDisabledState());
         assertEquals(false, cache3.getDisabledState());
 
         // Create a new cache1.  Verify that the new instance is not disabled.
-        cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
-                    @Override
-                    public Boolean recompute(Integer x) {
-                        return tester.query(x);
-                    }
-                };
+        cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+                new ServerQuery(tester));
         assertEquals(false, cache1.getDisabledState());
     }
 
-    private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
+    private static class TestQuery
+            extends PropertyInvalidatedCache.QueryHandler<Integer, String> {
 
-    private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
-        TestCache() {
-            this(CACHE_PROPERTY);
-        }
-
-        TestCache(String key) {
-            super(4, key);
-            setTestMode(true);
-            testPropertyName(key);
-        }
+        private int mRecomputeCount = 0;
 
         @Override
-        public String recompute(Integer qv) {
+        public String apply(Integer qv) {
             mRecomputeCount += 1;
             return "foo" + qv.toString();
         }
@@ -222,15 +205,40 @@
         int getRecomputeCount() {
             return mRecomputeCount;
         }
+    }
 
-        private int mRecomputeCount = 0;
+    private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
+        private final TestQuery mQuery;
+
+        TestCache() {
+            this(MODULE, API);
+        }
+
+        TestCache(int module, String api) {
+            this(module, api, new TestQuery());
+            setTestMode(true);
+            testPropertyName();
+        }
+
+        TestCache(int module, String api, TestQuery query) {
+            super(4, module, api, api, query);
+            mQuery = query;
+            setTestMode(true);
+            testPropertyName();
+        }
+
+        public int getRecomputeCount() {
+            return mQuery.getRecomputeCount();
+        }
+
+
     }
 
     @Test
     public void testCacheRecompute() {
         TestCache cache = new TestCache();
         cache.invalidateCache();
-        assertEquals(cache.isDisabledLocal(), false);
+        assertEquals(cache.isDisabled(), false);
         assertEquals("foo5", cache.query(5));
         assertEquals(1, cache.getRecomputeCount());
         assertEquals("foo5", cache.query(5));
@@ -241,6 +249,11 @@
         assertEquals("foo5", cache.query(5));
         assertEquals("foo5", cache.query(5));
         assertEquals(3, cache.getRecomputeCount());
+        // Invalidate the cache with a direct call to the property.
+        PropertyInvalidatedCache.invalidateCache(MODULE, API);
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(4, cache.getRecomputeCount());
     }
 
     @Test
@@ -257,7 +270,8 @@
 
     @Test
     public void testCachePropertyUnset() {
-        TestCache cache = new TestCache(UNSET_KEY);
+        final String UNSET_API = "otherApi";
+        TestCache cache = new TestCache(MODULE, UNSET_API);
         assertEquals("foo5", cache.query(5));
         assertEquals("foo5", cache.query(5));
         assertEquals(2, cache.getRecomputeCount());
@@ -327,17 +341,40 @@
     @Test
     public void testLocalProcessDisable() {
         TestCache cache = new TestCache();
-        assertEquals(cache.isDisabledLocal(), false);
+        assertEquals(cache.isDisabled(), false);
         cache.invalidateCache();
         assertEquals("foo5", cache.query(5));
         assertEquals(1, cache.getRecomputeCount());
         assertEquals("foo5", cache.query(5));
         assertEquals(1, cache.getRecomputeCount());
-        assertEquals(cache.isDisabledLocal(), false);
+        assertEquals(cache.isDisabled(), false);
         cache.disableLocal();
-        assertEquals(cache.isDisabledLocal(), true);
+        assertEquals(cache.isDisabled(), true);
         assertEquals("foo5", cache.query(5));
         assertEquals("foo5", cache.query(5));
         assertEquals(3, cache.getRecomputeCount());
     }
+
+    @Test
+    public void testPropertyNames() {
+        String n1;
+        n1 = PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
+        assertEquals(n1, "cache_key.system_server.get_package_info");
+        n1 = PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
+        assertEquals(n1, "cache_key.system_server.get_package_info");
+        try {
+            n1 = PropertyInvalidatedCache.createPropertyName(
+                PropertyInvalidatedCache.MODULE_SYSTEM - 1, "get package_info");
+            // n1 is an invalid api name.
+            assertEquals(false, true);
+        } catch (IllegalArgumentException e) {
+            // An exception is expected here.
+        }
+
+        n1 = PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
+        assertEquals(n1, "cache_key.bluetooth.get_state");
+    }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index b2c4274..5c9044c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -646,6 +646,10 @@
         }
 
         @Override
+        public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+        }
+
+        @Override
         public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
         }
 
diff --git a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
index b66642c..fa657f7 100644
--- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
+++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
@@ -71,6 +71,8 @@
     private Resources mResources;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Drawable mDrawable;
+    @Mock
+    private PackageManager mPackageManager;
     private CrossProfileApps mCrossProfileApps;
 
     @Before
@@ -87,6 +89,7 @@
                 Context.DEVICE_POLICY_SERVICE);
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mDevicePolicyManager);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
     }
 
     @Before
@@ -131,7 +134,8 @@
         setValidTargetProfile(MANAGED_PROFILE);
 
         mCrossProfileApps.getProfileSwitchingIconDrawable(MANAGED_PROFILE);
-        verify(mResources).getDrawable(R.drawable.ic_corp_badge, null);
+        verify(mPackageManager).getUserBadgeForDensityNoBackground(
+                MANAGED_PROFILE, /* density= */0);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
similarity index 85%
rename from core/tests/coretests/src/android/content/pm/PackageHelperTests.java
rename to core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
index 947da0b..0629a99 100644
--- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
@@ -25,7 +25,7 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 
 import org.mockito.Mockito;
 
@@ -36,10 +36,9 @@
 import java.util.UUID;
 
 @Presubmit
-public class PackageHelperTests extends AndroidTestCase {
+public class InstallLocationUtilsTests extends AndroidTestCase {
     private static final boolean localLOGV = true;
     public static final String TAG = "PackageHelperTests";
-    protected final String PREFIX = "android.content.pm";
 
     private static final String sInternalVolPath = "/data";
     private static final String sAdoptedVolPath = "/mnt/expand/123";
@@ -88,11 +87,14 @@
         UUID internalUuid = UUID.randomUUID();
         UUID adoptedUuid = UUID.randomUUID();
         UUID publicUuid = UUID.randomUUID();
-        Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize);
+        Mockito.when(storageManager.getStorageBytesUntilLow(internalFile))
+                .thenReturn(sInternalSize);
         Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize);
         Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize);
-        Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile))).thenReturn(internalUuid);
-        Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile))).thenReturn(adoptedUuid);
+        Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile)))
+                .thenReturn(internalUuid);
+        Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile)))
+                .thenReturn(adoptedUuid);
         Mockito.when(storageManager.getUuidForPath(Mockito.eq(publicFile))).thenReturn(publicUuid);
         Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(internalUuid), Mockito.anyInt()))
                 .thenReturn(sInternalSize);
@@ -103,7 +105,7 @@
         return storageManager;
     }
 
-    private static final class MockedInterface extends PackageHelper.TestableInterface {
+    private static final class MockedInterface extends InstallLocationUtils.TestableInterface {
         private boolean mForceAllowOnExternal = false;
         private boolean mAllow3rdPartyOnInternal = true;
         private ApplicationInfo mApplicationInfo = null;
@@ -164,25 +166,25 @@
         mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
 
         mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
 
         mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
 
         mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
 
@@ -192,7 +194,7 @@
         mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch(IOException e) {
@@ -202,7 +204,7 @@
         mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch(IOException e) {
@@ -212,7 +214,7 @@
         mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch(IOException e) {
@@ -222,7 +224,7 @@
         mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch(IOException e) {
@@ -240,13 +242,13 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sInternalVolUuid, volume);
     }
@@ -260,25 +262,25 @@
         appInfo.volumeUuid = sAdoptedVolUuid;
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
     }
@@ -292,7 +294,7 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
             fail("Expected exception was not thrown " + appInfo.volumeUuid);
         } catch (IOException e) {
@@ -302,7 +304,7 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
             fail("Expected exception was not thrown " + appInfo.volumeUuid);
         } catch (IOException e) {
@@ -312,7 +314,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
             fail("Expected exception was not thrown " + appInfo.volumeUuid);
         } catch (IOException e) {
@@ -322,7 +324,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         try {
-            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
             fail("Expected exception was not thrown " + appInfo.volumeUuid);
         } catch (IOException e) {
@@ -336,28 +338,28 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the volume with bigger available space.
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the volume with bigger available space.
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the volume with bigger available space.
         assertEquals(sAdoptedVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the volume with bigger available space.
         assertEquals(sAdoptedVolUuid, volume);
@@ -371,20 +373,20 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 true /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sInternalVolUuid, volume);
 
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         try {
-            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch (IOException e) {
@@ -395,7 +397,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
     }
@@ -407,7 +409,7 @@
         mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the non-internal volume.
         assertEquals(sAdoptedVolUuid, volume);
@@ -415,7 +417,7 @@
         appInfo = null;
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
         // Should return the non-internal volume.
         assertEquals(sAdoptedVolUuid, volume);
@@ -428,7 +430,7 @@
                 true /*allow 3rd party on internal*/);
         String volume = null;
         try {
-            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location internal ONLY*/,
                     1000000 /*size too big*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
@@ -445,7 +447,7 @@
                 false /*allow 3rd party on internal*/);
         String volume = null;
         try {
-            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
                     1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
             fail("Expected exception in resolveInstallVolume was not thrown");
         } catch (IOException e) {
@@ -456,7 +458,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
 
@@ -474,7 +476,7 @@
         mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
                 false /*allow 3rd party on internal*/);
         String volume = null;
-        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+        volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
             1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
         assertEquals(sAdoptedVolUuid, volume);
     }
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 104f077..f7ca822 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -146,6 +146,7 @@
 
     @Test
     public void testValidateWaveformBuilder() {
+        // Cover builder methods
         VibrationEffect.startWaveform(targetAmplitude(1))
                 .addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100))
                 .addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200))
@@ -158,6 +159,39 @@
                 .build()
                 .validate();
 
+        // Make sure class summary javadoc examples compile and are valid.
+        // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE WaveformBuilder javadocs.
+        VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+                .build()
+                .validate();
+        VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addOffDuration(Duration.ofMillis(20))
+                .repeatEffectIndefinitely(
+                        VibrationEffect.startWaveform(targetAmplitude(0.2f))
+                                .addSustain(Duration.ofMillis(10))
+                                .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+                                .addSustain(Duration.ofMillis(30))
+                                .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+                                .addSustain(Duration.ofMillis(50))
+                                .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+                                .build())
+                .compose()
+                .validate();
+        VibrationEffect.createWaveform(new long[]{10, 20, 30}, new int[]{51, 102, 204}, -1)
+                .validate();
+        VibrationEffect.startWaveform(targetAmplitude(0.2f))
+                .addSustain(Duration.ofMillis(10))
+                .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+                .addSustain(Duration.ofMillis(20))
+                .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+                .addSustain(Duration.ofMillis(30))
+                .build()
+                .validate();
+
         assertThrows(IllegalStateException.class,
                 () -> VibrationEffect.startWaveform().build().validate());
         assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2));
@@ -171,6 +205,7 @@
 
     @Test
     public void testValidateComposed() {
+        // Cover builder methods
         VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .addEffect(TEST_ONE_SHOT)
@@ -178,11 +213,28 @@
                 .addOffDuration(Duration.ofMillis(100))
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0))
+                .compose()
+                .validate();
+        VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(TEST_ONE_SHOT)
                 .compose()
                 .validate();
 
+        // Make sure class summary javadoc examples compile and are valid.
+        // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE Composition javadocs.
         VibrationEffect.startComposition()
-                .repeatEffectIndefinitely(TEST_ONE_SHOT)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+                .compose()
+                .validate();
+        VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+                .addOffDuration(Duration.ofMillis(10))
+                .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+                .addOffDuration(Duration.ofMillis(50))
+                .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0))
                 .compose()
                 .validate();
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 2018836..99670d9 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -58,7 +58,7 @@
 
     // The number of flags held in boolean properties. Their values should also be double-checked
     // in the methods above.
-    private static final int NUM_BOOLEAN_PROPERTIES = 23;
+    private static final int NUM_BOOLEAN_PROPERTIES = 24;
 
     @Test
     public void testStandardActions_serializationFlagIsValid() {
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 059c764..00b3693 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,8 +16,12 @@
 
 package android.widget;
 
+import static com.android.internal.R.id.pending_intent_tag;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -31,7 +35,10 @@
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Looper;
 import android.os.Parcel;
+import android.util.SizeF;
+import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -49,6 +56,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -261,6 +269,148 @@
         verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2");
     }
 
+    @Test
+    public void nestedViews_setRemoteAdapter_intent() {
+        Looper.prepare();
+
+        AppWidgetHostView widget = new AppWidgetHostView(mContext);
+        RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+        inner2.setRemoteAdapter(R.id.list, new Intent());
+        inner1.addView(R.id.container, inner2);
+        top.addView(R.id.container, inner1);
+
+        View view = top.apply(mContext, widget);
+        widget.addView(view);
+
+        ListView listView = (ListView) view.findViewById(R.id.list);
+        listView.onRemoteAdapterConnected();
+        assertNotNull(listView.getAdapter());
+
+        top.reapply(mContext, view);
+        listView = (ListView) view.findViewById(R.id.list);
+        assertNotNull(listView.getAdapter());
+    }
+
+    @Test
+    public void nestedViews_setRemoteAdapter_remoteCollectionItems() {
+        AppWidgetHostView widget = new AppWidgetHostView(mContext);
+        RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+        inner2.setRemoteAdapter(
+                R.id.list,
+                new RemoteViews.RemoteCollectionItems.Builder()
+                    .addItem(0, new RemoteViews(mPackage, R.layout.remote_view_host))
+                    .build());
+        inner1.addView(R.id.container, inner2);
+        top.addView(R.id.container, inner1);
+
+        View view = top.apply(mContext, widget);
+        widget.addView(view);
+
+        ListView listView = (ListView) view.findViewById(R.id.list);
+        assertNotNull(listView.getAdapter());
+
+        top.reapply(mContext, view);
+        listView = (ListView) view.findViewById(R.id.list);
+        assertNotNull(listView.getAdapter());
+    }
+
+    @Test
+    public void nestedViews_collectionChildFlag() throws Exception {
+        RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+        nested.setOnClickPendingIntent(
+                R.id.text,
+                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+        );
+
+        RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host);
+        listItem.addView(R.id.container, nested);
+        listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+        View view = listItem.apply(mContext, mContainer);
+        TextView text = (TextView) view.findViewById(R.id.text);
+        assertNull(text.getTag(pending_intent_tag));
+    }
+
+    @Test
+    public void landscapePortraitViews_collectionChildFlag() throws Exception {
+        RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+        inner.setOnClickPendingIntent(
+                R.id.text,
+                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+        );
+
+        RemoteViews listItem = new RemoteViews(inner, inner);
+        listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+        View view = listItem.apply(mContext, mContainer);
+        TextView text = (TextView) view.findViewById(R.id.text);
+        assertNull(text.getTag(pending_intent_tag));
+    }
+
+    @Test
+    public void sizedViews_collectionChildFlag() throws Exception {
+        RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+        inner.setOnClickPendingIntent(
+                R.id.text,
+                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+        );
+
+        RemoteViews listItem = new RemoteViews(
+                Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+        listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+        View view = listItem.apply(mContext, mContainer);
+        TextView text = (TextView) view.findViewById(R.id.text);
+        assertNull(text.getTag(pending_intent_tag));
+    }
+
+    @Test
+    public void nestedViews_lightBackgroundLayoutFlag() {
+        RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+        nested.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+        RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_view_host);
+        parent.addView(R.id.container, nested);
+        parent.setLightBackgroundLayoutId(R.layout.remote_view_host);
+        parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+        View view = parent.apply(mContext, mContainer);
+        assertNull(view.findViewById(R.id.text));
+        assertNotNull(view.findViewById(R.id.light_background_text));
+    }
+
+
+    @Test
+    public void landscapePortraitViews_lightBackgroundLayoutFlag() {
+        RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+        inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+        RemoteViews parent = new RemoteViews(inner, inner);
+        parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+        View view = parent.apply(mContext, mContainer);
+        assertNull(view.findViewById(R.id.text));
+        assertNotNull(view.findViewById(R.id.light_background_text));
+    }
+
+    @Test
+    public void sizedViews_lightBackgroundLayoutFlag() {
+        RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+        inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+        RemoteViews parent = new RemoteViews(
+                Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+        parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+        View view = parent.apply(mContext, mContainer);
+        assertNull(view.findViewById(R.id.text));
+        assertNotNull(view.findViewById(R.id.light_background_text));
+    }
+
     private RemoteViews createViewChained(int depth, String... texts) {
         RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
 
@@ -483,6 +633,47 @@
                 index, inflated.getTag(com.android.internal.R.id.notification_action_index_tag));
     }
 
+    @Test
+    public void nestedViews_themesPropagateCorrectly() {
+        Context themedContext =
+                new ContextThemeWrapper(mContext, R.style.RelativeLayoutAlignBottom50Alpha);
+        RelativeLayout rootParent = new RelativeLayout(themedContext);
+
+        RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+        RemoteViews inner1 =
+                new RemoteViews(mPackage, R.layout.remote_view_relative_layout_with_theme);
+        RemoteViews inner2 =
+                new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+
+        inner1.addView(R.id.themed_layout, inner2);
+        top.addView(R.id.container, inner1);
+
+        RelativeLayout root = (RelativeLayout) top.apply(themedContext, rootParent);
+        assertEquals(0.5, root.getAlpha(), 0.);
+        RelativeLayout.LayoutParams rootParams =
+                (RelativeLayout.LayoutParams) root.getLayoutParams();
+        assertEquals(RelativeLayout.TRUE,
+                rootParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+        // The theme is set on inner1View and its descendants. However, inner1View does
+        // not get its layout params from its theme (though its descendants do), but other
+        // attributes such as alpha are set.
+        RelativeLayout inner1View = (RelativeLayout) root.getChildAt(0);
+        assertEquals(R.id.themed_layout, inner1View.getId());
+        assertEquals(0.25, inner1View.getAlpha(), 0.);
+        RelativeLayout.LayoutParams inner1Params =
+                (RelativeLayout.LayoutParams) inner1View.getLayoutParams();
+        assertEquals(RelativeLayout.TRUE,
+                inner1Params.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+        RelativeLayout inner2View = (RelativeLayout) inner1View.getChildAt(0);
+        assertEquals(0.25, inner2View.getAlpha(), 0.);
+        RelativeLayout.LayoutParams inner2Params =
+                (RelativeLayout.LayoutParams) inner2View.getLayoutParams();
+        assertEquals(RelativeLayout.TRUE,
+                inner2Params.getRule(RelativeLayout.ALIGN_PARENT_TOP));
+    }
+
     private class WidgetContainer extends AppWidgetHostView {
         int[] mSharedViewIds;
         String[] mSharedViewNames;
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 75d2025..2817728f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -731,6 +731,25 @@
     }
 
     @Test
+    public void testMiniResolver() {
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(1);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(1);
+        // Personal profile only has a browser
+        personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed()));
+    }
+
+    @Test
     public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index b655369..f5cbffb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.os;
 
 import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
 import static android.os.BatteryStats.STATS_SINCE_CHARGED;
 import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
 
@@ -30,6 +31,12 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.telephony.Annotation;
+import android.telephony.CellSignalStrength;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.view.Display;
 
@@ -1165,6 +1172,185 @@
                 "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
     }
 
+    @SmallTest
+    public void testGetPerStateActiveRadioDurationMs() {
+        final MockClock clock = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
+        final int ratCount = BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT;
+        final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
+        final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+
+        final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        for (int rat = 0; rat < ratCount; rat++) {
+            for (int freq = 0; freq < frequencyCount; freq++) {
+                for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+                    expectedDurationsMs[rat][freq][txLvl] = 0;
+                }
+            }
+        }
+
+        class ModemAndBatteryState {
+            public long currentTimeMs = 100;
+            public boolean onBattery = false;
+            public boolean modemActive = false;
+            @Annotation.NetworkType
+            public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+            @BatteryStats.RadioAccessTechnology
+            public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+            @ServiceState.FrequencyRange
+            public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+            public SparseIntArray currentSignalStrengths = new SparseIntArray();
+
+            void setOnBattery(boolean onBattery) {
+                this.onBattery = onBattery;
+                bi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000,
+                        currentTimeMs * 1000);
+            }
+
+            void setModemActive(boolean active) {
+                modemActive = active;
+                final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+                        : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+                bi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID);
+            }
+
+            void setRatType(@Annotation.NetworkType int dataType,
+                    @BatteryStats.RadioAccessTechnology int rat) {
+                currentNetworkDataType = dataType;
+                currentRat = rat;
+                bi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE,
+                        currentFrequencyRange);
+            }
+
+            void setFrequencyRange(@ServiceState.FrequencyRange int frequency) {
+                currentFrequencyRange = frequency;
+                bi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true,
+                        ServiceState.STATE_IN_SERVICE, frequency);
+            }
+
+            void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) {
+                currentSignalStrengths.put(rat, strength);
+                final int size = currentSignalStrengths.size();
+                final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1);
+                bi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths);
+            }
+        }
+        final ModemAndBatteryState state = new ModemAndBatteryState();
+
+        IntConsumer incrementTime = inc -> {
+            state.currentTimeMs += inc;
+            clock.realtime = clock.uptime = state.currentTimeMs;
+
+            // If the device is not on battery, no timers should increment.
+            if (!state.onBattery) return;
+            // If the modem is not active, no timers should increment.
+            if (!state.modemActive) return;
+
+            final int currentRat = state.currentRat;
+            final int currentFrequencyRange =
+                    currentRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0;
+            int currentSignalStrength = state.currentSignalStrengths.get(currentRat);
+            expectedDurationsMs[currentRat][currentFrequencyRange][currentSignalStrength] += inc;
+        };
+
+        state.setOnBattery(false);
+        state.setModemActive(false);
+        state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN);
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // While not on battery, the timers should not increase.
+        state.setModemActive(true);
+        incrementTime.accept(100);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+        incrementTime.accept(200);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        incrementTime.accept(500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE);
+        incrementTime.accept(300);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setRatType(TelephonyManager.NETWORK_TYPE_LTE,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE);
+        incrementTime.accept(400);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+        incrementTime.accept(500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should
+        // start counting up.
+        state.setOnBattery(true);
+        incrementTime.accept(600);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Changing LTE signal strength should be tracked.
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        incrementTime.accept(700);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        incrementTime.accept(800);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        incrementTime.accept(900);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+        incrementTime.accept(1000);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Change in the signal strength of nonactive RAT should not affect anything.
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        incrementTime.accept(1100);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Changing to OTHER Rat should start tracking the poor signal strength.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+        incrementTime.accept(1200);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Noting frequency change should not affect non NR Rat.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH);
+        incrementTime.accept(1300);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Now the NR Rat, HIGH frequency range, good signal strength should start counting.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+        incrementTime.accept(1400);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Noting frequency change should not affect non NR Rat.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
+        incrementTime.accept(1500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+        // Modem no longer active, should not be tracking any more.
+        state.setModemActive(false);
+        incrementTime.accept(1500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
@@ -1238,4 +1424,30 @@
                     bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED));
         }
     }
+
+    private void checkPerStateActiveRadioDurations(long[][][] expectedDurationsMs,
+            BatteryStatsImpl bi, long currentTimeMs) {
+        for (int rat = 0; rat < expectedDurationsMs.length; rat++) {
+            final long[][] expectedRatDurationsMs = expectedDurationsMs[rat];
+            for (int freq = 0; freq < expectedRatDurationsMs.length; freq++) {
+                final long[] expectedFreqDurationsMs = expectedRatDurationsMs[freq];
+                for (int strength = 0; strength < expectedFreqDurationsMs.length; strength++) {
+                    final long expectedSignalStrengthDurationMs = expectedFreqDurationsMs[strength];
+                    final long actualDurationMs = bi.getActiveRadioDurationMs(rat, freq,
+                            strength, currentTimeMs);
+
+                    // Build a verbose fail message, just in case.
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append("Wrong time in state for RAT:");
+                    sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+                    sb.append(", frequency:");
+                    sb.append(ServiceState.frequencyRangeToString(freq));
+                    sb.append(", strength:");
+                    sb.append(strength);
+
+                    assertEquals(sb.toString(), expectedSignalStrengthDurationMs, actualDurationMs);
+                }
+            }
+        }
+    }
 }
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index db63e6e..4c247427 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -38,7 +38,6 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Mockito;
 
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -155,7 +154,7 @@
         verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
         Mockito.reset(callback);
 
-        mDeviceStateManagerGlobal.cancelRequest(request);
+        mDeviceStateManagerGlobal.cancelStateRequest();
 
         verify(callback).onStateChanged(eq(mService.getBaseState()));
     }
@@ -172,7 +171,7 @@
         verify(callback).onRequestActivated(eq(request));
         Mockito.reset(callback);
 
-        mDeviceStateManagerGlobal.cancelRequest(request);
+        mDeviceStateManagerGlobal.cancelStateRequest();
 
         verify(callback).onRequestCanceled(eq(request));
     }
@@ -203,13 +202,13 @@
 
         private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
         private int mBaseState = DEFAULT_DEVICE_STATE;
-        private ArrayList<Request> mRequests = new ArrayList<>();
+        private Request mRequest;
 
         private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
 
         private DeviceStateInfo getInfo() {
-            final int mergedState = mRequests.isEmpty()
-                    ? mBaseState : mRequests.get(mRequests.size() - 1).state;
+            final int mergedState = mRequest == null
+                    ? mBaseState : mRequest.state;
             return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState);
         }
 
@@ -245,11 +244,10 @@
 
         @Override
         public void requestState(IBinder token, int state, int flags) {
-            if (!mRequests.isEmpty()) {
-                final Request topRequest = mRequests.get(mRequests.size() - 1);
+            if (mRequest != null) {
                 for (IDeviceStateManagerCallback callback : mCallbacks) {
                     try {
-                        callback.onRequestSuspended(topRequest.token);
+                        callback.onRequestCanceled(mRequest.token);
                     } catch (RemoteException e) {
                         // Do nothing. Should never happen.
                     }
@@ -257,7 +255,7 @@
             }
 
             final Request request = new Request(token, state, flags);
-            mRequests.add(request);
+            mRequest = request;
             notifyDeviceStateInfoChanged();
 
             for (IDeviceStateManagerCallback callback : mCallbacks) {
@@ -270,20 +268,9 @@
         }
 
         @Override
-        public void cancelRequest(IBinder token) {
-            int index = -1;
-            for (int i = 0; i < mRequests.size(); i++) {
-                if (mRequests.get(i).token.equals(token)) {
-                    index = i;
-                    break;
-                }
-            }
-
-            if (index == -1) {
-                throw new IllegalArgumentException("Unknown request: " + token);
-            }
-
-            mRequests.remove(index);
+        public void cancelStateRequest() {
+            IBinder token = mRequest.token;
+            mRequest = null;
             for (IDeviceStateManagerCallback callback : mCallbacks) {
                 try {
                     callback.onRequestCanceled(token);
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index f04a9f7..e16a2f8 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -288,9 +288,8 @@
         }
 
         @Override
-        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
-                final int deviceType) {
-        }
+        public void addVendorCommandListener(
+                final IHdmiVendorCommandListener listener, final int vendorId) {}
 
         @Override
         public void sendVendorCommand(final int deviceType, final int targetAddress,
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index ddcab6e..5dcc599 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -61,5 +61,6 @@
         <permission name="android.permission.READ_DREAM_SUPPRESSION"/>
         <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+        <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index d95644a..ae350ec 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -74,5 +74,6 @@
         <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
         <permission name="android.permission.FORCE_STOP_PACKAGES" />
         <permission name="android.permission.ACCESS_FPS_COUNTER" />
+        <permission name="android.permission.CHANGE_CONFIGURATION" />
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 88920c8..92fca36 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -231,18 +231,6 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
     </split-permission>
-    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
-                      targetSdk="33">
-        <new-permission name="android.permission.READ_MEDIA_AUDIO" />
-    </split-permission>
-    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
-                      targetSdk="33">
-        <new-permission name="android.permission.READ_MEDIA_VIDEO" />
-    </split-permission>
-    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
-                      targetSdk="33">
-        <new-permission name="android.permission.READ_MEDIA_IMAGE" />
-    </split-permission>
     <split-permission name="android.permission.BLUETOOTH"
                       targetSdk="31">
         <new-permission name="android.permission.BLUETOOTH_SCAN" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b3dcc34..d002601 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -581,6 +581,7 @@
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+        <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.bips">
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 4ae0fc4..5a3a033 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -272,13 +272,13 @@
 
     <!-- fallback fonts -->
     <family lang="und-Arab" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+        <font weight="400" style="normal">
             NotoNaskhArabic-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
     </family>
     <family lang="und-Arab" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+        <font weight="400" style="normal">
             NotoNaskhArabicUI-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
@@ -329,7 +329,7 @@
         <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifThai">
+        <font weight="400" style="normal" fallbackFor="serif">
             NotoSerifThai-Regular.ttf
         </font>
         <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
@@ -923,16 +923,16 @@
         <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
     </family>
     <family lang="und-Laoo" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansLao">NotoSansLao-Regular.ttf
+        <font weight="400" style="normal">NotoSansLao-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
-        <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifLao">
+        <font weight="400" style="normal" fallbackFor="serif">
             NotoSerifLao-Regular.ttf
         </font>
         <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
     </family>
     <family lang="und-Laoo" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+        <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
     </family>
@@ -1013,7 +1013,7 @@
         </font>
     </family>
     <family lang="und-Cans">
-        <font weight="400" style="normal" postScriptName="NotoSansCanadianAboriginal">
+        <font weight="400" style="normal">
             NotoSansCanadianAboriginal-Regular.ttf
         </font>
     </family>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 055e5ad..857af11 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -458,7 +458,7 @@
          * No color information is stored.
          * With this configuration, each pixel requires 1 byte of memory.
          */
-        ALPHA_8     (1),
+        ALPHA_8(1),
 
         /**
          * Each pixel is stored on 2 bytes and only the RGB channels are
@@ -479,7 +479,7 @@
          * short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f);
          * </pre>
          */
-        RGB_565     (3),
+        RGB_565(3),
 
         /**
          * Each pixel is stored on 2 bytes. The three RGB color channels
@@ -501,7 +501,7 @@
          *             it is advised to use {@link #ARGB_8888} instead.
          */
         @Deprecated
-        ARGB_4444   (4),
+        ARGB_4444(4),
 
         /**
          * Each pixel is stored on 4 bytes. Each channel (RGB and alpha
@@ -516,10 +516,10 @@
          * int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff);
          * </pre>
          */
-        ARGB_8888   (5),
+        ARGB_8888(5),
 
         /**
-         * Each pixels is stored on 8 bytes. Each channel (RGB and alpha
+         * Each pixel is stored on 8 bytes. Each channel (RGB and alpha
          * for translucency) is stored as a
          * {@link android.util.Half half-precision floating point value}.
          *
@@ -531,7 +531,7 @@
          * long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff);
          * </pre>
          */
-        RGBA_F16    (6),
+        RGBA_F16(6),
 
         /**
          * Special configuration, when bitmap is stored only in graphic memory.
@@ -540,13 +540,29 @@
          * It is optimal for cases, when the only operation with the bitmap is to draw it on a
          * screen.
          */
-        HARDWARE    (7);
+        HARDWARE(7),
+
+        /**
+         * Each pixel is stored on 4 bytes. Each RGB channel is stored with 10 bits of precision
+         * (1024 possible values). There is an additional alpha channel that is stored with 2 bits
+         * of precision (4 possible values).
+         *
+         * This configuration is suited for wide-gamut and HDR content which does not require alpha
+         * blending, such that the memory cost is the same as ARGB_8888 while enabling higher color
+         * precision.
+         *
+         * <p>Use this formula to pack into 32 bits:</p>
+         * <pre class="prettyprint">
+         * int color = (A & 0x3) << 30 | (B & 0x3ff) << 20 | (G & 0x3ff) << 10 | (R & 0x3ff);
+         * </pre>
+         */
+        RGBA_1010102(8);
 
         @UnsupportedAppUsage
         final int nativeInt;
 
         private static Config sConfigs[] = {
-            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE
+            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
         };
 
         Config(int ni) {
@@ -1000,8 +1016,8 @@
      * @param width    The width of the bitmap
      * @param height   The height of the bitmap
      * @param config   The bitmap config to create.
-     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
-     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
+     * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+     *                 used to mark the bitmap as opaque. Doing so will clear the bitmap in black
      *                 instead of transparent.
      *
      * @throws IllegalArgumentException if the width or height are <= 0, or if
@@ -1019,8 +1035,8 @@
      * @param width    The width of the bitmap
      * @param height   The height of the bitmap
      * @param config   The bitmap config to create.
-     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
-     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
+     * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+     *                 used to mark the bitmap as opaque. Doing so will clear the bitmap in black
      *                 instead of transparent.
      * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}
      *                   and {@link ColorSpace.Named#SRGB sRGB} or
@@ -1050,8 +1066,8 @@
      * @param width    The width of the bitmap
      * @param height   The height of the bitmap
      * @param config   The bitmap config to create.
-     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
-     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
+     * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+     *                 used to mark the bitmap as opaque. Doing so will clear the bitmap in black
      *                 instead of transparent.
      *
      * @throws IllegalArgumentException if the width or height are <= 0, or if
@@ -1074,8 +1090,8 @@
      * @param width    The width of the bitmap
      * @param height   The height of the bitmap
      * @param config   The bitmap config to create.
-     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
-     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
+     * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+     *                 used to mark the bitmap as opaque. Doing so will clear the bitmap in black
      *                 instead of transparent.
      * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}
      *                   and {@link ColorSpace.Named#SRGB sRGB} or
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index cdf746f..f440b69 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -454,7 +454,8 @@
      * @param challenge is a non-empty byte array whose contents should be unique, fresh and
      *                  provided by the issuing authority. The value provided is embedded in the
      *                  generated CBOR and enables the issuing authority to verify that the
-     *                  returned proof is fresh.
+     *                  returned proof is fresh. Implementations are required to support
+     *                  challenges at least 32 bytes of length.
      * @return the COSE_Sign1 data structure above
      */
     public @NonNull byte[] proveOwnership(@NonNull byte[] challenge)  {
@@ -485,7 +486,8 @@
      * @param challenge is a non-empty byte array whose contents should be unique, fresh and
      *                  provided by the issuing authority. The value provided is embedded in the
      *                  generated CBOR and enables the issuing authority to verify that the
-     *                  returned proof is fresh.
+     *                  returned proof is fresh. Implementations are required to support
+     *                  challenges at least 32 bytes of length.
      * @return the COSE_Sign1 data structure above
      */
     public @NonNull byte[] delete(@NonNull byte[] challenge)  {
diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java
index 305d0ea..6d56964 100644
--- a/identity/java/android/security/identity/WritableIdentityCredential.java
+++ b/identity/java/android/security/identity/WritableIdentityCredential.java
@@ -59,7 +59,8 @@
      * @param challenge is a non-empty byte array whose contents should be unique, fresh and
      *                  provided by the issuing authority. The value provided is embedded in the
      *                  attestation extension and enables the issuing authority to verify that the
-     *                  attestation certificate is fresh.
+     *                  attestation certificate is fresh. Implementations are required to support
+     *                  challenges at least 32 bytes of length.
      * @return the X.509 certificate for this credential's CredentialKey.
      */
     public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index e2bc360..9384e2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -245,7 +245,8 @@
         }
     }
 
-    private void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+    private void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+            List<Rect> unrestricted) {
         synchronized (mDisplays) {
             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
                 Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown"
@@ -253,7 +254,8 @@
                 return;
             }
             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
-                mDisplayChangedListeners.get(i).onKeepClearAreasChanged(displayId, keepClearAreas);
+                mDisplayChangedListeners.get(i)
+                    .onKeepClearAreasChanged(displayId, restricted, unrestricted);
             }
         }
     }
@@ -318,9 +320,10 @@
         }
 
         @Override
-        public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+        public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+                List<Rect> unrestricted) {
             mMainExecutor.execute(() -> {
-                DisplayController.this.onKeepClearAreasChanged(displayId, keepClearAreas);
+                DisplayController.this.onKeepClearAreasChanged(displayId, restricted, unrestricted);
             });
         }
     }
@@ -361,6 +364,7 @@
         /**
          * Called when keep-clear areas on a display have changed.
          */
-        default void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
+        default void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+                List<Rect> unrestricted) {}
     }
 }
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 f61e624..9f4ff7c 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
@@ -471,9 +471,10 @@
     static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
             DisplayController displayController, Context context,
             @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler,
             @ShellAnimationThread ShellExecutor animExecutor) {
         return new Transitions(organizer, pool, displayController, context, mainExecutor,
-                animExecutor);
+                mainHandler, animExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index eda09e3..5ebdceb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -150,43 +150,45 @@
 
             if (inLandscape) {
                 final Rect leftHitRegion = new Rect();
-                final Rect leftDrawRegion = topOrLeftBounds;
                 final Rect rightHitRegion = new Rect();
-                final Rect rightDrawRegion = bottomOrRightBounds;
 
                 // If we have existing split regions use those bounds, otherwise split it 50/50
                 if (inSplitScreen) {
-                    // Add the divider bounds to each side since that counts for the hit region.
-                    leftHitRegion.set(topOrLeftBounds);
-                    leftHitRegion.right += dividerWidth / 2;
-                    rightHitRegion.set(bottomOrRightBounds);
-                    rightHitRegion.left -= dividerWidth / 2;
+                    // The bounds of the existing split will have a divider bar, the hit region
+                    // should include that space. Find the center of the divider bar:
+                    float centerX = topOrLeftBounds.right + (dividerWidth / 2);
+                    // Now set the hit regions using that center.
+                    leftHitRegion.set(displayRegion);
+                    leftHitRegion.right = (int) centerX;
+                    rightHitRegion.set(displayRegion);
+                    rightHitRegion.left = (int) centerX;
                 } else {
                     displayRegion.splitVertically(leftHitRegion, rightHitRegion);
                 }
 
-                mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
-                mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
+                mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds));
+                mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds));
 
             } else {
                 final Rect topHitRegion = new Rect();
-                final Rect topDrawRegion = topOrLeftBounds;
                 final Rect bottomHitRegion = new Rect();
-                final Rect bottomDrawRegion = bottomOrRightBounds;
 
                 // If we have existing split regions use those bounds, otherwise split it 50/50
                 if (inSplitScreen) {
-                    // Add the divider bounds to each side since that counts for the hit region.
-                    topHitRegion.set(topOrLeftBounds);
-                    topHitRegion.bottom += dividerWidth / 2;
-                    bottomHitRegion.set(bottomOrRightBounds);
-                    bottomHitRegion.top -= dividerWidth / 2;
+                    // The bounds of the existing split will have a divider bar, the hit region
+                    // should include that space. Find the center of the divider bar:
+                    float centerX = topOrLeftBounds.bottom + (dividerWidth / 2);
+                    // Now set the hit regions using that center.
+                    topHitRegion.set(displayRegion);
+                    topHitRegion.bottom = (int) centerX;
+                    bottomHitRegion.set(displayRegion);
+                    bottomHitRegion.top = (int) centerX;
                 } else {
                     displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
                 }
 
-                mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
-                mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
+                mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds));
+                mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds));
             }
         } else {
             // Split-screen not allowed, so only show the fullscreen target
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 fd3be2b..7307ba3 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
@@ -24,7 +24,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -103,7 +102,7 @@
                 MATCH_PARENT));
         ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
         ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
-        updateContainerMargins();
+        updateContainerMargins(getResources().getConfiguration().orientation);
     }
 
     @Override
@@ -128,20 +127,18 @@
     }
 
     public void onConfigChanged(Configuration newConfig) {
-        final int orientation = getResources().getConfiguration().orientation;
-        if (orientation == Configuration.ORIENTATION_LANDSCAPE
+        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
                 && getOrientation() != HORIZONTAL) {
             setOrientation(LinearLayout.HORIZONTAL);
-            updateContainerMargins();
-        } else if (orientation == Configuration.ORIENTATION_PORTRAIT
+            updateContainerMargins(newConfig.orientation);
+        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
                 && getOrientation() != VERTICAL) {
             setOrientation(LinearLayout.VERTICAL);
-            updateContainerMargins();
+            updateContainerMargins(newConfig.orientation);
         }
     }
 
-    private void updateContainerMargins() {
-        final int orientation = getResources().getConfiguration().orientation;
+    private void updateContainerMargins(int orientation) {
         final float halfMargin = mDisplayMargin / 2f;
         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
             mDropZoneView1.setContainerMargin(
@@ -156,10 +153,6 @@
         }
     }
 
-    public boolean hasDropTarget() {
-        return mCurrentTarget != null;
-    }
-
     public boolean hasDropped() {
         return mHasDropped;
     }
@@ -271,6 +264,9 @@
      * Updates the visible drop target as the user drags.
      */
     public void update(DragEvent event) {
+        if (mHasDropped) {
+            return;
+        }
         // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
         // visibility of the current region
         DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
@@ -286,7 +282,8 @@
                 animateHighlight(target);
             } else {
                 // Switching between targets
-                animateHighlight(target);
+                mDropZoneView1.animateSwitch();
+                mDropZoneView2.animateSwitch();
             }
             mCurrentTarget = target;
         }
@@ -323,7 +320,7 @@
                 : DISABLE_NONE);
         mDropZoneView1.setShowingMargin(visible);
         mDropZoneView2.setShowingMargin(visible);
-        ObjectAnimator animator = mDropZoneView1.getAnimator();
+        Animator animator = mDropZoneView1.getAnimator();
         if (animCompleteCallback != null) {
             if (animator != null) {
                 animator.addListener(new AnimatorListenerAdapter() {
@@ -343,17 +340,11 @@
         if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT
                 || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) {
             mDropZoneView1.setShowingHighlight(true);
-            mDropZoneView1.setShowingSplash(false);
-
             mDropZoneView2.setShowingHighlight(false);
-            mDropZoneView2.setShowingSplash(true);
         } else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT
                 || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) {
             mDropZoneView1.setShowingHighlight(false);
-            mDropZoneView1.setShowingSplash(true);
-
             mDropZoneView2.setShowingHighlight(true);
-            mDropZoneView2.setShowingSplash(false);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 2f47af5..a3ee8ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -18,6 +18,7 @@
 
 import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
 
+import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -27,7 +28,6 @@
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
-import android.util.IntProperty;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -43,8 +43,8 @@
  */
 public class DropZoneView extends FrameLayout {
 
-    private static final int SPLASHSCREEN_ALPHA_INT = (int) (255 * 0.90f);
-    private static final int HIGHLIGHT_ALPHA_INT = 255;
+    private static final float SPLASHSCREEN_ALPHA = 0.90f;
+    private static final float HIGHLIGHT_ALPHA = 1f;
     private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
     private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;
 
@@ -61,54 +61,27 @@
                 }
             };
 
-    private static final IntProperty<ColorDrawable> SPLASHSCREEN_ALPHA =
-            new IntProperty<ColorDrawable>("splashscreen") {
-                @Override
-                public void setValue(ColorDrawable d, int alpha) {
-                    d.setAlpha(alpha);
-                }
-
-                @Override
-                public Integer get(ColorDrawable d) {
-                    return d.getAlpha();
-                }
-            };
-
-    private static final IntProperty<ColorDrawable> HIGHLIGHT_ALPHA =
-            new IntProperty<ColorDrawable>("highlight") {
-                @Override
-                public void setValue(ColorDrawable d, int alpha) {
-                    d.setAlpha(alpha);
-                }
-
-                @Override
-                public Integer get(ColorDrawable d) {
-                    return d.getAlpha();
-                }
-            };
-
     private final Path mPath = new Path();
     private final float[] mContainerMargin = new float[4];
     private float mCornerRadius;
     private float mBottomInset;
     private int mMarginColor; // i.e. color used for negative space like the container insets
-    private int mHighlightColor;
 
     private boolean mShowingHighlight;
     private boolean mShowingSplash;
     private boolean mShowingMargin;
 
-    // TODO: might be more seamless to animate between splash/highlight color instead of 2 separate
-    private ObjectAnimator mSplashAnimator;
-    private ObjectAnimator mHighlightAnimator;
+    private int mSplashScreenColor;
+    private int mHighlightColor;
+
+    private ObjectAnimator mBackgroundAnimator;
     private ObjectAnimator mMarginAnimator;
     private float mMarginPercent;
 
     // Renders a highlight or neutral transparent color
-    private ColorDrawable mDropZoneDrawable;
+    private ColorDrawable mColorDrawable;
     // Renders the translucent splashscreen with the app icon in the middle
     private ImageView mSplashScreenView;
-    private ColorDrawable mSplashBackgroundDrawable;
     // Renders the margin / insets around the dropzone container
     private MarginView mMarginView;
 
@@ -130,19 +103,14 @@
 
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mMarginColor = getResources().getColor(R.color.taskbar_background);
-        mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
-
-        mDropZoneDrawable = new ColorDrawable();
-        mDropZoneDrawable.setColor(mHighlightColor);
-        mDropZoneDrawable.setAlpha(0);
-        setBackgroundDrawable(mDropZoneDrawable);
+        int c = getResources().getColor(android.R.color.system_accent1_500);
+        mHighlightColor =  Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
+        mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
+        mColorDrawable = new ColorDrawable();
+        setBackgroundDrawable(mColorDrawable);
 
         mSplashScreenView = new ImageView(context);
         mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER);
-        mSplashBackgroundDrawable = new ColorDrawable();
-        mSplashBackgroundDrawable.setColor(Color.WHITE);
-        mSplashBackgroundDrawable.setAlpha(SPLASHSCREEN_ALPHA_INT);
-        mSplashScreenView.setBackgroundDrawable(mSplashBackgroundDrawable);
         addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT));
         mSplashScreenView.setAlpha(0f);
@@ -157,10 +125,6 @@
         mMarginColor = getResources().getColor(R.color.taskbar_background);
         mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
 
-        final int alpha = mDropZoneDrawable.getAlpha();
-        mDropZoneDrawable.setColor(mHighlightColor);
-        mDropZoneDrawable.setAlpha(alpha);
-
         if (mMarginPercent > 0) {
             mMarginView.invalidate();
         }
@@ -187,38 +151,39 @@
     }
 
     /** Sets the color and icon to use for the splashscreen when shown. */
-    public void setAppInfo(int splashScreenColor, Drawable appIcon) {
-        mSplashBackgroundDrawable.setColor(splashScreenColor);
+    public void setAppInfo(int color, Drawable appIcon) {
+        Color c = Color.valueOf(color);
+        mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue());
         mSplashScreenView.setImageDrawable(appIcon);
     }
 
     /** @return an active animator for this view if one exists. */
     @Nullable
-    public ObjectAnimator getAnimator() {
+    public Animator getAnimator() {
         if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
             return mMarginAnimator;
-        } else if (mHighlightAnimator != null && mHighlightAnimator.isRunning()) {
-            return mHighlightAnimator;
-        } else if (mSplashAnimator != null && mSplashAnimator.isRunning()) {
-            return mSplashAnimator;
+        } else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) {
+            return mBackgroundAnimator;
         }
         return null;
     }
 
-    /** Animates the splashscreen to show or hide. */
-    public void setShowingSplash(boolean showingSplash) {
-        if (mShowingSplash != showingSplash) {
-            mShowingSplash = showingSplash;
-            animateSplashToState();
-        }
+    /** Animates between highlight and splashscreen depending on current state. */
+    public void animateSwitch() {
+        mShowingHighlight = !mShowingHighlight;
+        mShowingSplash = !mShowingHighlight;
+        final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+        animateBackground(mColorDrawable.getColor(), newColor);
+        animateSplashScreenIcon();
     }
 
     /** Animates the highlight indicating the zone is hovered on or not. */
     public void setShowingHighlight(boolean showingHighlight) {
-        if (mShowingHighlight != showingHighlight) {
-            mShowingHighlight = showingHighlight;
-            animateHighlightToState();
-        }
+        mShowingHighlight = showingHighlight;
+        mShowingSplash = !mShowingHighlight;
+        final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+        animateBackground(Color.TRANSPARENT, newColor);
+        animateSplashScreenIcon();
     }
 
     /** Animates the margins around the drop zone to show or hide. */
@@ -228,40 +193,31 @@
             animateMarginToState();
         }
         if (!mShowingMargin) {
-            setShowingHighlight(false);
-            setShowingSplash(false);
+            mShowingHighlight = false;
+            mShowingSplash = false;
+            animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT);
+            animateSplashScreenIcon();
         }
     }
 
-    private void animateSplashToState() {
-        if (mSplashAnimator != null) {
-            mSplashAnimator.cancel();
+    private void animateBackground(int startColor, int endColor) {
+        if (mBackgroundAnimator != null) {
+            mBackgroundAnimator.cancel();
         }
-        mSplashAnimator = ObjectAnimator.ofInt(mSplashBackgroundDrawable,
-                SPLASHSCREEN_ALPHA,
-                mSplashBackgroundDrawable.getAlpha(),
-                mShowingSplash ? SPLASHSCREEN_ALPHA_INT : 0);
-        if (!mShowingSplash) {
-            mSplashAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+        mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable,
+                "color",
+                startColor,
+                endColor);
+        if (!mShowingSplash && !mShowingHighlight) {
+            mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
         }
-        mSplashAnimator.start();
+        mBackgroundAnimator.start();
+    }
+
+    private void animateSplashScreenIcon() {
         mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
     }
 
-    private void animateHighlightToState() {
-        if (mHighlightAnimator != null) {
-            mHighlightAnimator.cancel();
-        }
-        mHighlightAnimator = ObjectAnimator.ofInt(mDropZoneDrawable,
-                HIGHLIGHT_ALPHA,
-                mDropZoneDrawable.getAlpha(),
-                mShowingHighlight ? HIGHLIGHT_ALPHA_INT : 0);
-        if (!mShowingHighlight) {
-            mHighlightAnimator.setInterpolator(FAST_OUT_SLOW_IN);
-        }
-        mHighlightAnimator.start();
-    }
-
     private void animateMarginToState() {
         if (mMarginAnimator != null) {
             mMarginAnimator.cancel();
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 17005ea..6b0d7f5 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
@@ -867,6 +867,10 @@
     }
 
     private void fadeExistingPip(boolean show) {
+        if (mLeash == null || !mLeash.isValid()) {
+            Log.w(TAG, "Invalid leash on fadeExistingPip: " + mLeash);
+            return;
+        }
         final float alphaStart = show ? 0 : 1;
         final float alphaEnd = show ? 1 : 0;
         mPipAnimationController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e592101..a2c2f59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -879,6 +879,7 @@
         if (mMainUnfoldController != null && mSideUnfoldController != null) {
             mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
             mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+            updateUnfoldBounds();
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 79c8a87..ddf01a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -25,6 +25,11 @@
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -57,16 +62,27 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.view.Choreographer;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowManager;
@@ -92,6 +108,8 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
 
 /** The default handler that handles anything not already handled. */
 public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@@ -118,6 +136,7 @@
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
     private final TransitionAnimation mTransitionAnimation;
+    private final DevicePolicyManager mDevicePolicyManager;
 
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
 
@@ -132,9 +151,24 @@
 
     private ScreenRotationAnimation mRotationAnimation;
 
+    private Drawable mEnterpriseThumbnailDrawable;
+
+    private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            boolean isDrawable = intent.getBooleanExtra(
+                    EXTRA_RESOURCE_TYPE_DRAWABLE, /* default= */ false);
+            if (!isDrawable) {
+                return;
+            }
+            updateEnterpriseThumbnailDrawable();
+        }
+    };
+
     DefaultTransitionHandler(@NonNull DisplayController displayController,
             @NonNull TransactionPool transactionPool, Context context,
-            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+            @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+            @NonNull ShellExecutor animExecutor) {
         mDisplayController = displayController;
         mTransactionPool = transactionPool;
         mContext = context;
@@ -143,9 +177,23 @@
         mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
         mCurrentUserId = UserHandle.myUserId();
 
+        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+        updateEnterpriseThumbnailDrawable();
+        mContext.registerReceiver(
+                mEnterpriseResourceUpdatedReceiver,
+                new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
+                /* broadcastPermission = */ null,
+                mainHandler);
+
         AttributeCache.init(context);
     }
 
+    private void updateEnterpriseThumbnailDrawable() {
+        mEnterpriseThumbnailDrawable = mDevicePolicyManager.getDrawable(
+                WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+                () -> mContext.getDrawable(R.drawable.ic_corp_badge));
+    }
+
     @VisibleForTesting
     static boolean isRotationSeamless(@NonNull TransitionInfo info,
             DisplayController displayController) {
@@ -290,6 +338,9 @@
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
         };
 
+        final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
+                new ArrayList<>();
+
         @ColorInt int backgroundColorForTransition = 0;
         final int wallpaperTransit = getWallpaperTransitType(info);
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -361,13 +412,15 @@
                     }
                 }
 
-                float cornerRadius = 0;
+                final float cornerRadius;
                 if (a.hasRoundedCorners() && isTask) {
                     // hasRoundedCorners is currently only enabled for tasks
                     final Context displayContext =
                             mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
                     cornerRadius =
                             ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
+                } else {
+                    cornerRadius = 0;
                 }
 
                 if (a.getShowBackground()) {
@@ -383,12 +436,37 @@
                     }
                 }
 
+                boolean delayedEdgeExtension = false;
+                if (!isTask && a.hasExtension()) {
+                    if (!Transitions.isOpeningType(change.getMode())) {
+                        // Can screenshot now (before startTransaction is applied)
+                        edgeExtendWindow(change, a, startTransaction, finishTransaction);
+                    } else {
+                        // Need to screenshot after startTransaction is applied otherwise activity
+                        // may not be visible or ready yet.
+                        postStartTransactionCallbacks
+                                .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
+                        delayedEdgeExtension = true;
+                    }
+                }
+
                 final Rect clipRect = Transitions.isClosingType(change.getMode())
                         ? mRotator.getEndBoundsInStartRotation(change)
                         : change.getEndAbsBounds();
-                startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
-                        mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
-                        cornerRadius, clipRect);
+
+                if (delayedEdgeExtension) {
+                    // If the edge extension needs to happen after the startTransition has been
+                    // applied, then we want to only start the animation after the edge extension
+                    // postStartTransaction callback has been run
+                    postStartTransactionCallbacks.add(t ->
+                            startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+                                    mTransactionPool, mMainExecutor, mAnimExecutor,
+                                    null /* position */, cornerRadius, clipRect));
+                } else {
+                    startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+                            mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
+                            cornerRadius, clipRect);
+                }
 
                 if (info.getAnimationOptions() != null) {
                     attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(),
@@ -402,7 +480,20 @@
                     startTransaction, finishTransaction);
         }
 
-        startTransaction.apply();
+        // postStartTransactionCallbacks require that the start transaction is already
+        // applied to run otherwise they may result in flickers and UI inconsistencies.
+        boolean waitForStartTransactionApply = postStartTransactionCallbacks.size() > 0;
+        startTransaction.apply(waitForStartTransactionApply);
+
+        // Run tasks that require startTransaction to already be applied
+        for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+                postStartTransactionCallbacks) {
+            final SurfaceControl.Transaction t = mTransactionPool.acquire();
+            postStartTransactionCallback.accept(t);
+            t.apply();
+            mTransactionPool.release(t);
+        }
+
         mRotator.cleanUp(finishTransaction);
         TransitionMetrics.getInstance().reportAnimationStart(transition);
         // run finish now in-case there are no animations
@@ -410,6 +501,117 @@
         return true;
     }
 
+    private void edgeExtendWindow(TransitionInfo.Change change,
+            Animation a, SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction) {
+        final Transformation transformationAtStart = new Transformation();
+        a.getTransformationAt(0, transformationAtStart);
+        final Transformation transformationAtEnd = new Transformation();
+        a.getTransformationAt(1, transformationAtEnd);
+
+        // We want to create an extension surface that is the maximal size and the animation will
+        // take care of cropping any part that overflows.
+        final Insets maxExtensionInsets = Insets.min(
+                transformationAtStart.getInsets(), transformationAtEnd.getInsets());
+
+        final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
+                change.getEndAbsBounds().height());
+        final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
+                change.getEndAbsBounds().width());
+        if (maxExtensionInsets.left < 0) {
+            final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+            final Rect extensionRect = new Rect(0, 0,
+                    -maxExtensionInsets.left, targetSurfaceHeight);
+            final int xPos = maxExtensionInsets.left;
+            final int yPos = 0;
+            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+                    "Left Edge Extension", startTransaction, finishTransaction);
+        }
+
+        if (maxExtensionInsets.top < 0) {
+            final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+            final Rect extensionRect = new Rect(0, 0,
+                    targetSurfaceWidth, -maxExtensionInsets.top);
+            final int xPos = 0;
+            final int yPos = maxExtensionInsets.top;
+            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+                    "Top Edge Extension", startTransaction, finishTransaction);
+        }
+
+        if (maxExtensionInsets.right < 0) {
+            final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
+                    targetSurfaceWidth, targetSurfaceHeight);
+            final Rect extensionRect = new Rect(0, 0,
+                    -maxExtensionInsets.right, targetSurfaceHeight);
+            final int xPos = targetSurfaceWidth;
+            final int yPos = 0;
+            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+                    "Right Edge Extension", startTransaction, finishTransaction);
+        }
+
+        if (maxExtensionInsets.bottom < 0) {
+            final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
+                    targetSurfaceWidth, targetSurfaceHeight);
+            final Rect extensionRect = new Rect(0, 0,
+                    targetSurfaceWidth, -maxExtensionInsets.bottom);
+            final int xPos = maxExtensionInsets.left;
+            final int yPos = targetSurfaceHeight;
+            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+                    "Bottom Edge Extension", startTransaction, finishTransaction);
+        }
+    }
+
+    private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
+            Rect extensionRect, int xPos, int yPos, String layerName,
+            SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction) {
+        final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+                .setName(layerName)
+                .setParent(surfaceToExtend)
+                .setHidden(true)
+                .setCallsite("DefaultTransitionHandler#startAnimation")
+                .setOpaque(true)
+                .setBufferSize(extensionRect.width(), extensionRect.height())
+                .build();
+
+        SurfaceControl.LayerCaptureArgs captureArgs =
+                new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
+                        .setSourceCrop(edgeBounds)
+                        .setFrameScale(1)
+                        .setPixelFormat(PixelFormat.RGBA_8888)
+                        .setChildrenOnly(true)
+                        .setAllowProtected(true)
+                        .build();
+        final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
+                SurfaceControl.captureLayers(captureArgs);
+
+        if (edgeBuffer == null) {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "Failed to capture edge of window.");
+            return null;
+        }
+
+        android.graphics.BitmapShader shader =
+                new android.graphics.BitmapShader(edgeBuffer.asBitmap(),
+                        android.graphics.Shader.TileMode.CLAMP,
+                        android.graphics.Shader.TileMode.CLAMP);
+        final Paint paint = new Paint();
+        paint.setShader(shader);
+
+        final Surface surface = new Surface(edgeExtensionLayer);
+        Canvas c = surface.lockHardwareCanvas();
+        c.drawRect(extensionRect, paint);
+        surface.unlockCanvasAndPost(c);
+        surface.release();
+
+        startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
+        startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
+        startTransaction.setVisibility(edgeExtensionLayer, true);
+        finishTransaction.remove(edgeExtensionLayer);
+
+        return edgeExtensionLayer;
+    }
+
     private void addBackgroundToTransition(
             @NonNull SurfaceControl rootLeash,
             @ColorInt int color,
@@ -632,7 +834,7 @@
         final boolean isClose = Transitions.isClosingType(change.getMode());
         if (isOpen) {
             if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
-                attachCrossProfileThunmbnailAnimation(animations, finishCallback, change,
+                attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
                         cornerRadius);
             } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
                 attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius);
@@ -642,13 +844,14 @@
         }
     }
 
-    private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
+    private void attachCrossProfileThumbnailAnimation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) {
-        final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
-                ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
         final Rect bounds = change.getEndAbsBounds();
+        // Show the right drawable depending on the user we're transitioning to.
+        final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId
+                ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable;
         final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
-                thumbnailDrawableRes, bounds);
+                thumbnailDrawable, bounds);
         if (thumbnail == null) {
             return;
         }
@@ -736,9 +939,17 @@
         }
         t.setMatrix(leash, transformation.getMatrix(), matrix);
         t.setAlpha(leash, transformation.getAlpha());
+
+        Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+        if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
+            // Clip out any overflowing edge extension
+            clipRect.inset(extensionInsets);
+            t.setCrop(leash, clipRect);
+        }
+
         if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
             // We can only apply rounded corner if a crop is set
-            t.setWindowCrop(leash, clipRect);
+            t.setCrop(leash, clipRect);
             t.setCornerRadius(leash, cornerRadius);
         }
 
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 33a98b2..86b73fc 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
@@ -33,6 +33,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -123,7 +124,8 @@
 
     public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
             @NonNull DisplayController displayController, @NonNull Context context,
-            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+            @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+            @NonNull ShellExecutor animExecutor) {
         mOrganizer = organizer;
         mContext = context;
         mMainExecutor = mainExecutor;
@@ -132,7 +134,7 @@
         mPlayerImpl = new TransitionPlayerImpl();
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
-                animExecutor));
+                mainHandler, animExecutor));
         // Next lowest priority is remote transitions.
         mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
         mHandlers.add(mRemoteTransitionHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 825320b..a6caefe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -363,6 +363,45 @@
     }
 
     @Test
+    public void testOnEligibleForLetterboxEducationActivityChanged() {
+        final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+        taskInfo1.displayId = DEFAULT_DISPLAY;
+        taskInfo1.topActivityEligibleForLetterboxEducation = false;
+        final TrackingTaskListener taskListener = new TrackingTaskListener();
+        mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+        mOrganizer.onTaskAppeared(taskInfo1, null);
+
+        // Task listener sent to compat UI is null if top activity isn't eligible for letterbox
+        // education.
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+
+        // Task listener is non-null if top activity is eligible for letterbox education and task
+        // is visible.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo2 =
+                createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+        taskInfo2.displayId = taskInfo1.displayId;
+        taskInfo2.topActivityEligibleForLetterboxEducation = true;
+        taskInfo2.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo2);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+
+        // Task listener is null if task is invisible.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo3 =
+                createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+        taskInfo3.displayId = taskInfo1.displayId;
+        taskInfo3.topActivityEligibleForLetterboxEducation = true;
+        taskInfo3.isVisible = false;
+        mOrganizer.onTaskInfoChanged(taskInfo3);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+
+        clearInvocations(mCompatUI);
+        mOrganizer.onTaskVanished(taskInfo1);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+    }
+
+    @Test
     public void testOnCameraCompatActivityChanged() {
         final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
         taskInfo1.displayId = DEFAULT_DISPLAY;
@@ -375,7 +414,7 @@
         // compat control.
         verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
 
-        // Task linster is non-null when request a camera compat control for a visible task.
+        // Task listener is non-null when request a camera compat control for a visible task.
         clearInvocations(mCompatUI);
         final RunningTaskInfo taskInfo2 =
                 createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e391713..0f4a06f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -54,7 +54,9 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.view.IDisplayWindowListener;
 import android.view.IWindowManager;
@@ -84,8 +86,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 
@@ -106,6 +106,7 @@
     private final TestShellExecutor mMainExecutor = new TestShellExecutor();
     private final ShellExecutor mAnimExecutor = new TestShellExecutor();
     private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
 
     @Before
     public void setUp() {
@@ -752,7 +753,7 @@
 
     private Transitions createTestTransitions() {
         return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
-                mContext, mMainExecutor, mAnimExecutor);
+                mContext, mMainExecutor, mMainHandler, mAnimExecutor);
     }
 //
 //    private class TestDisplayController extends DisplayController {
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index db3a108..dd272cd 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -287,29 +287,29 @@
     std::mutex mVkLock;
 };
 
+static bool checkSupport(AHardwareBuffer_Format format) {
+    AHardwareBuffer_Desc desc = {
+            .width = 1,
+            .height = 1,
+            .layers = 1,
+            .format = format,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
+                     AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
+    };
+    UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc);
+    return buffer != nullptr;
+}
+
 bool HardwareBitmapUploader::hasFP16Support() {
-    static std::once_flag sOnce;
-    static bool hasFP16Support = false;
-
-    // Gralloc shouldn't let us create a USAGE_HW_TEXTURE if GLES is unable to consume it, so
-    // we don't need to double-check the GLES version/extension.
-    std::call_once(sOnce, []() {
-        AHardwareBuffer_Desc desc = {
-                .width = 1,
-                .height = 1,
-                .layers = 1,
-                .format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
-                .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
-                         AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
-                         AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
-        };
-        UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc);
-        hasFP16Support = buffer != nullptr;
-    });
-
+    static bool hasFP16Support = checkSupport(AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT);
     return hasFP16Support;
 }
 
+bool HardwareBitmapUploader::has1010102Support() {
+    static bool has101012Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM);
+    return has101012Support;
+}
+
 static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
     FormatInfo formatInfo;
     switch (skBitmap.info().colorType()) {
@@ -350,6 +350,19 @@
             formatInfo.type = GL_UNSIGNED_BYTE;
             formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
             break;
+        case kRGBA_1010102_SkColorType:
+            formatInfo.isSupported = HardwareBitmapUploader::has1010102Support();
+            if (formatInfo.isSupported) {
+                formatInfo.type = GL_UNSIGNED_INT_2_10_10_10_REV;
+                formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
+                formatInfo.vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+            } else {
+                formatInfo.type = GL_UNSIGNED_BYTE;
+                formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+                formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
+            }
+            formatInfo.format = GL_RGBA;
+            break;
         default:
             ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType());
             formatInfo.valid = false;
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index ad7a95a..34f43cd 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -29,10 +29,12 @@
 
 #ifdef __ANDROID__
     static bool hasFP16Support();
+    static bool has1010102Support();
 #else
     static bool hasFP16Support() {
         return true;
     }
+    static bool has1010102Support() { return true; }
 #endif
 };
 
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index 3780ba0..bc6bc45 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -57,6 +57,8 @@
             return ANDROID_BITMAP_FORMAT_A_8;
         case kRGBA_F16_SkColorType:
             return ANDROID_BITMAP_FORMAT_RGBA_F16;
+        case kRGBA_1010102_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGBA_1010102;
         default:
             return ANDROID_BITMAP_FORMAT_NONE;
     }
@@ -74,6 +76,8 @@
             return kAlpha_8_SkColorType;
         case ANDROID_BITMAP_FORMAT_RGBA_F16:
             return kRGBA_F16_SkColorType;
+        case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+            return kRGBA_1010102_SkColorType;
         default:
             return kUnknown_SkColorType;
     }
@@ -249,6 +253,9 @@
         case ANDROID_BITMAP_FORMAT_RGBA_F16:
             colorType = kRGBA_F16_SkColorType;
             break;
+        case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+            colorType = kRGBA_1010102_SkColorType;
+            break;
         default:
             return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
     }
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index fc542c8..dd68f82 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -159,6 +159,8 @@
             break;
         case kRGBA_F16_SkColorType:
             break;
+        case kRGBA_1010102_SkColorType:
+            break;
         default:
             return false;
     }
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 4cc05ef..1c20415 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -137,9 +137,16 @@
 
     auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle);
     SkColorType decodeColorType = brd->computeOutputColorType(colorType);
-    if (decodeColorType == kRGBA_F16_SkColorType && isHardware &&
+
+    if (isHardware) {
+        if (decodeColorType == kRGBA_F16_SkColorType &&
             !uirenderer::HardwareBitmapUploader::hasFP16Support()) {
-        decodeColorType = kN32_SkColorType;
+            decodeColorType = kN32_SkColorType;
+        }
+        if (decodeColorType == kRGBA_1010102_SkColorType &&
+            !uirenderer::HardwareBitmapUploader::has1010102Support()) {
+            decodeColorType = kN32_SkColorType;
+        }
     }
 
     // Set up the pixel allocator
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 77f46be..33669ac 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -365,6 +365,8 @@
             return kRGB_565_LegacyBitmapConfig;
         case kAlpha_8_SkColorType:
             return kA8_LegacyBitmapConfig;
+        case kRGBA_1010102_SkColorType:
+            return kRGBA_1010102_LegacyBitmapConfig;
         case kUnknown_SkColorType:
         default:
             break;
@@ -374,14 +376,10 @@
 
 SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) {
     const uint8_t gConfig2ColorType[] = {
-        kUnknown_SkColorType,
-        kAlpha_8_SkColorType,
-        kUnknown_SkColorType, // Previously kIndex_8_SkColorType,
-        kRGB_565_SkColorType,
-        kARGB_4444_SkColorType,
-        kN32_SkColorType,
-        kRGBA_F16_SkColorType,
-        kN32_SkColorType
+            kUnknown_SkColorType,  kAlpha_8_SkColorType,
+            kUnknown_SkColorType,  // Previously kIndex_8_SkColorType,
+            kRGB_565_SkColorType,  kARGB_4444_SkColorType, kN32_SkColorType,
+            kRGBA_F16_SkColorType, kN32_SkColorType,       kRGBA_1010102_SkColorType,
     };
 
     if (legacyConfig < 0 || legacyConfig > kLastEnum_LegacyBitmapConfig) {
@@ -399,15 +397,12 @@
     jint javaConfigId = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
 
     const AndroidBitmapFormat config2BitmapFormat[] = {
-        ANDROID_BITMAP_FORMAT_NONE,
-        ANDROID_BITMAP_FORMAT_A_8,
-        ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8
-        ANDROID_BITMAP_FORMAT_RGB_565,
-        ANDROID_BITMAP_FORMAT_RGBA_4444,
-        ANDROID_BITMAP_FORMAT_RGBA_8888,
-        ANDROID_BITMAP_FORMAT_RGBA_F16,
-        ANDROID_BITMAP_FORMAT_NONE // Congfig.HARDWARE
-    };
+            ANDROID_BITMAP_FORMAT_NONE,        ANDROID_BITMAP_FORMAT_A_8,
+            ANDROID_BITMAP_FORMAT_NONE,  // Previously Config.Index_8
+            ANDROID_BITMAP_FORMAT_RGB_565,     ANDROID_BITMAP_FORMAT_RGBA_4444,
+            ANDROID_BITMAP_FORMAT_RGBA_8888,   ANDROID_BITMAP_FORMAT_RGBA_F16,
+            ANDROID_BITMAP_FORMAT_NONE,  // Congfig.HARDWARE
+            ANDROID_BITMAP_FORMAT_RGBA_1010102};
     return config2BitmapFormat[javaConfigId];
 }
 
@@ -430,6 +425,9 @@
       case ANDROID_BITMAP_FORMAT_RGBA_F16:
         configId = kRGBA_16F_LegacyBitmapConfig;
         break;
+      case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+          configId = kRGBA_1010102_LegacyBitmapConfig;
+          break;
       default:
         break;
     }
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index ba407f2..085a905 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -34,16 +34,17 @@
     // This enum must keep these int values, to match the int values
     // in the java Bitmap.Config enum.
     enum LegacyBitmapConfig {
-        kNo_LegacyBitmapConfig          = 0,
-        kA8_LegacyBitmapConfig          = 1,
-        kIndex8_LegacyBitmapConfig      = 2,
-        kRGB_565_LegacyBitmapConfig     = 3,
-        kARGB_4444_LegacyBitmapConfig   = 4,
-        kARGB_8888_LegacyBitmapConfig   = 5,
-        kRGBA_16F_LegacyBitmapConfig    = 6,
-        kHardware_LegacyBitmapConfig    = 7,
+        kNo_LegacyBitmapConfig = 0,
+        kA8_LegacyBitmapConfig = 1,
+        kIndex8_LegacyBitmapConfig = 2,
+        kRGB_565_LegacyBitmapConfig = 3,
+        kARGB_4444_LegacyBitmapConfig = 4,
+        kARGB_8888_LegacyBitmapConfig = 5,
+        kRGBA_16F_LegacyBitmapConfig = 6,
+        kHardware_LegacyBitmapConfig = 7,
+        kRGBA_1010102_LegacyBitmapConfig = 8,
 
-        kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig
+        kLastEnum_LegacyBitmapConfig = kRGBA_1010102_LegacyBitmapConfig
     };
 
     static void setJavaVM(JavaVM* javaVM);
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 8ad8abc..25ed935 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,19 +16,18 @@
 #ifndef DRAWFRAMETASK_H
 #define DRAWFRAMETASK_H
 
-#include <optional>
-#include <vector>
-
-#include <performance_hint_private.h>
+#include <android/performance_hint.h>
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
 #include <utils/StrongPointer.h>
 
-#include "RenderTask.h"
+#include <optional>
+#include <vector>
 
 #include "../FrameInfo.h"
 #include "../Rect.h"
 #include "../TreeInfo.h"
+#include "RenderTask.h"
 
 namespace android {
 namespace uirenderer {
diff --git a/location/java/android/location/LastLocationRequest.java b/location/java/android/location/LastLocationRequest.java
index 73c5c82..fe0a14f 100644
--- a/location/java/android/location/LastLocationRequest.java
+++ b/location/java/android/location/LastLocationRequest.java
@@ -16,6 +16,9 @@
 
 package android.location;
 
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -220,8 +223,9 @@
          *
          * @hide
          */
+        // TODO: remove WRITE_SECURE_SETTINGS.
         @SystemApi
-        @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+        @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
         public @NonNull LastLocationRequest.Builder setAdasGnssBypass(boolean adasGnssBypass) {
             mAdasGnssBypass = adasGnssBypass;
             return this;
@@ -238,8 +242,9 @@
          *
          * @hide
          */
+        // TODO: remove WRITE_SECURE_SETTINGS.
         @SystemApi
-        @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+        @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
         public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
             mLocationSettingsIgnored = locationSettingsIgnored;
             return this;
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d275628..59c989b 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_BYPASS;
 import static android.Manifest.permission.LOCATION_HARDWARE;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.location.LocationRequest.createFromDeprecatedCriteria;
@@ -678,8 +679,9 @@
      *
      * @hide
      */
+    // TODO: remove WRITE_SECURE_SETTINGS.
     @SystemApi
-    @RequiresPermission(WRITE_SECURE_SETTINGS)
+    @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
     public void setAdasGnssLocationEnabled(boolean enabled) {
         try {
             mService.setAdasGnssLocationEnabledForUser(enabled, mContext.getUser().getIdentifier());
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 587222a..59f4f5e 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -16,6 +16,9 @@
 
 package android.location;
 
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
 import static java.lang.Math.max;
 import static java.lang.Math.min;
 
@@ -662,9 +665,10 @@
      * @hide
      * @deprecated LocationRequests should be treated as immutable.
      */
+    // TODO: remove WRITE_SECURE_SETTINGS.
     @SystemApi
     @Deprecated
-    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
     public @NonNull LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
         mBypass = locationSettingsIgnored;
         return this;
@@ -1132,8 +1136,9 @@
          *
          * @hide
          */
+        // TODO: remove WRITE_SECURE_SETTINGS
         @SystemApi
-        @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+        @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
         public @NonNull Builder setAdasGnssBypass(boolean adasGnssBypass) {
             mAdasGnssBypass = adasGnssBypass;
             return this;
@@ -1150,8 +1155,9 @@
          *
          * @hide
          */
+        // TODO: remove WRITE_SECURE_SETTINGS
         @SystemApi
-        @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+        @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
         public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
             mBypass = locationSettingsIgnored;
             return this;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a2704f9..15a398d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -181,6 +181,22 @@
     public static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
 
     /**
+     * @hide Broadcast intent when the volume for a particular stream type changes.
+     * Includes the stream, the new volume and previous volumes.
+     * Notes:
+     *  - for internal platform use only, do not make public,
+     *  - never used for "remote" volume changes
+     *
+     * @see #EXTRA_VOLUME_STREAM_TYPE
+     * @see #EXTRA_VOLUME_STREAM_VALUE
+     * @see #EXTRA_PREV_VOLUME_STREAM_VALUE
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @SuppressLint("ActionValue")
+    public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION";
+
+    /**
      * @hide Broadcast intent when the devices for a particular stream type changes.
      * Includes the stream, the new devices and previous devices.
      * Notes:
@@ -244,7 +260,8 @@
     /**
      * @hide The stream type for the volume changed intent.
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @SuppressLint("ActionValue")
     public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
 
     /**
@@ -261,7 +278,8 @@
     /**
      * @hide The volume associated with the stream for the volume changed intent.
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @SuppressLint("ActionValue")
     public static final String EXTRA_VOLUME_STREAM_VALUE =
         "android.media.EXTRA_VOLUME_STREAM_VALUE";
 
@@ -368,7 +386,7 @@
     public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
     /** @hide Used to identify the volume of audio streams for phone calls when connected
      *        to bluetooth */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
     /** @hide Used to identify the volume of audio streams for enforced system sounds
      *        in certain countries (e.g camera in Japan) */
@@ -544,6 +562,7 @@
      * Indicates the volume set/adjust call is for Bluetooth absolute volume
      * @hide
      */
+    @SystemApi
     public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6;
 
     /**
diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BtProfileConnectionInfo.java
index d1bb41e..88b9777 100644
--- a/media/java/android/media/BtProfileConnectionInfo.java
+++ b/media/java/android/media/BtProfileConnectionInfo.java
@@ -15,39 +15,25 @@
  */
 package android.media;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.bluetooth.BluetoothProfile;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Contains information about Bluetooth profile connection state changed
  * {@hide}
  */
 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class BtProfileConnectionInfo implements Parcelable {
-    /** @hide */
-    @IntDef({
-            BluetoothProfile.A2DP,
-            BluetoothProfile.A2DP_SINK,
-            BluetoothProfile.HEADSET, // Can only be set by BtHelper
-            BluetoothProfile.HEARING_AID,
-            BluetoothProfile.LE_AUDIO,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface BtProfile {}
 
-    private final @BtProfile int mProfile;
+    private final int mProfile;
     private final boolean mSupprNoisy;
     private final int mVolume;
     private final boolean mIsLeOutput;
 
-    private BtProfileConnectionInfo(@BtProfile int profile, boolean suppressNoisyIntent, int volume,
+    private BtProfileConnectionInfo(int profile, boolean suppressNoisyIntent, int volume,
             boolean isLeOutput) {
         mProfile = profile;
         mSupprNoisy = suppressNoisyIntent;
@@ -59,7 +45,7 @@
      * Constructor used by BtHelper when a profile is connected
      * {@hide}
      */
-    public BtProfileConnectionInfo(@BtProfile int profile) {
+    public BtProfileConnectionInfo(int profile) {
         this(profile, false, -1, false);
     }
 
@@ -142,7 +128,7 @@
     /**
      * @return The profile connection
      */
-    public @BtProfile int getProfile() {
+    public int getProfile() {
         return mProfile;
     }
 
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 5113dc2..71dc2a7 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -18,6 +18,7 @@
 
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2Info;
+import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 
 /**
@@ -27,7 +28,8 @@
     void notifySessionCreated(int requestId, in RoutingSessionInfo session);
     void notifySessionUpdated(in RoutingSessionInfo session);
     void notifySessionReleased(in RoutingSessionInfo session);
-    void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
+    void notifyDiscoveryPreferenceChanged(String packageName,
+            in RouteDiscoveryPreference discoveryPreference);
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 2427fa6..ee0293d 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -34,6 +34,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Describes the properties of a route.
@@ -340,10 +341,12 @@
     @ConnectionState
     final int mConnectionState;
     final String mClientPackageName;
+    final String mPackageName;
     final int mVolumeHandling;
     final int mVolumeMax;
     final int mVolume;
     final String mAddress;
+    final Set<String> mDeduplicationIds;
     final Bundle mExtras;
     final String mProviderId;
 
@@ -357,10 +360,12 @@
         mDescription = builder.mDescription;
         mConnectionState = builder.mConnectionState;
         mClientPackageName = builder.mClientPackageName;
+        mPackageName = builder.mPackageName;
         mVolumeHandling = builder.mVolumeHandling;
         mVolumeMax = builder.mVolumeMax;
         mVolume = builder.mVolume;
         mAddress = builder.mAddress;
+        mDeduplicationIds = builder.mDeduplicationIds;
         mExtras = builder.mExtras;
         mProviderId = builder.mProviderId;
     }
@@ -375,10 +380,12 @@
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mConnectionState = in.readInt();
         mClientPackageName = in.readString();
+        mPackageName = in.readString();
         mVolumeHandling = in.readInt();
         mVolumeMax = in.readInt();
         mVolume = in.readInt();
         mAddress = in.readString();
+        mDeduplicationIds = Set.of(in.readStringArray());
         mExtras = in.readBundle();
         mProviderId = in.readString();
     }
@@ -486,6 +493,17 @@
     }
 
     /**
+     * Gets the package name of the provider that published the route.
+     * <p>
+     * It is set by the system service.
+     * @hide
+     */
+    @Nullable
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
      * Gets information about how volume is handled on the route.
      *
      * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
@@ -518,6 +536,18 @@
         return mAddress;
     }
 
+    /**
+     * Gets the Deduplication ID of the route if available.
+     * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+     */
+    @NonNull
+    public Set<String> getDeduplicationIds() {
+        return mDeduplicationIds;
+    }
+
+    /**
+     * Gets an optional bundle with extra data.
+     */
     @Nullable
     public Bundle getExtras() {
         return mExtras == null ? null : new Bundle(mExtras);
@@ -549,7 +579,7 @@
      * Returns if the route has at least one of the specified route features.
      *
      * @param features the list of route features to consider
-     * @return true if the route has at least one feature in the list
+     * @return {@code true} if the route has at least one feature in the list
      * @hide
      */
     public boolean hasAnyFeatures(@NonNull Collection<String> features) {
@@ -563,6 +593,21 @@
     }
 
     /**
+     * Returns if the route has all the specified route features.
+     *
+     * @hide
+     */
+    public boolean hasAllFeatures(@NonNull Collection<String> features) {
+        Objects.requireNonNull(features, "features must not be null");
+        for (String feature : features) {
+            if (!getFeatures().contains(feature)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns true if the route info has all of the required field.
      * A route is valid if and only if it is obtained from
      * {@link com.android.server.media.MediaRouterService}.
@@ -596,10 +641,12 @@
                 && Objects.equals(mDescription, other.mDescription)
                 && (mConnectionState == other.mConnectionState)
                 && Objects.equals(mClientPackageName, other.mClientPackageName)
+                && Objects.equals(mPackageName, other.mPackageName)
                 && (mVolumeHandling == other.mVolumeHandling)
                 && (mVolumeMax == other.mVolumeMax)
                 && (mVolume == other.mVolume)
                 && Objects.equals(mAddress, other.mAddress)
+                && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
                 && Objects.equals(mProviderId, other.mProviderId);
     }
 
@@ -607,8 +654,8 @@
     public int hashCode() {
         // Note: mExtras is not included.
         return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
-                mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
-                mAddress, mProviderId);
+                mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
+                mVolume, mAddress, mDeduplicationIds, mProviderId);
     }
 
     @Override
@@ -626,6 +673,7 @@
                 .append(", volumeHandling=").append(getVolumeHandling())
                 .append(", volumeMax=").append(getVolumeMax())
                 .append(", volume=").append(getVolume())
+                .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
                 .append(", providerId=").append(getProviderId())
                 .append(" }");
         return result.toString();
@@ -647,10 +695,12 @@
         TextUtils.writeToParcel(mDescription, dest, flags);
         dest.writeInt(mConnectionState);
         dest.writeString(mClientPackageName);
+        dest.writeString(mPackageName);
         dest.writeInt(mVolumeHandling);
         dest.writeInt(mVolumeMax);
         dest.writeInt(mVolume);
         dest.writeString(mAddress);
+        dest.writeStringArray(mDeduplicationIds.toArray(new String[mDeduplicationIds.size()]));
         dest.writeBundle(mExtras);
         dest.writeString(mProviderId);
     }
@@ -671,10 +721,12 @@
         @ConnectionState
         int mConnectionState;
         String mClientPackageName;
+        String mPackageName;
         int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
         int mVolumeMax;
         int mVolume;
         String mAddress;
+        Set<String> mDeduplicationIds;
         Bundle mExtras;
         String mProviderId;
 
@@ -698,6 +750,7 @@
             mId = id;
             mName = name;
             mFeatures = new ArrayList<>();
+            mDeduplicationIds = Set.of();
         }
 
         /**
@@ -733,10 +786,12 @@
             mDescription = routeInfo.mDescription;
             mConnectionState = routeInfo.mConnectionState;
             mClientPackageName = routeInfo.mClientPackageName;
+            mPackageName = routeInfo.mPackageName;
             mVolumeHandling = routeInfo.mVolumeHandling;
             mVolumeMax = routeInfo.mVolumeMax;
             mVolume = routeInfo.mVolume;
             mAddress = routeInfo.mAddress;
+            mDeduplicationIds = Set.copyOf(routeInfo.mDeduplicationIds);
             if (routeInfo.mExtras != null) {
                 mExtras = new Bundle(routeInfo.mExtras);
             }
@@ -860,6 +915,16 @@
         }
 
         /**
+         * Sets the package name of the route.
+         * @hide
+         */
+        @NonNull
+        public Builder setPackageName(@NonNull String packageName) {
+            mPackageName = packageName;
+            return this;
+        }
+
+        /**
          * Sets the route's volume handling.
          */
         @NonNull
@@ -897,6 +962,20 @@
         }
 
         /**
+         * Sets the deduplication ID of the route.
+         * Routes have the same ID could be removed even when
+         * they are from different providers.
+         * <p>
+         * If it's {@code null}, the route will not be removed.
+         * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+         */
+        @NonNull
+        public Builder setDeduplicationIds(@NonNull Set<String> id) {
+            mDeduplicationIds = Set.copyOf(id);
+            return this;
+        }
+
+        /**
          * Sets a bundle of extras for the route.
          * <p>
          * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 4b32dbf..b485eb5 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -34,15 +34,18 @@
 import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -302,8 +305,7 @@
         mSystemController = new SystemRoutingController(
                 ensureClientPackageNameForSystemSession(
                         sManager.getSystemRoutingSession(clientPackageName)));
-        mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
-                sManager.getPreferredFeatures(clientPackageName), true).build();
+        mDiscoveryPreference = sManager.getDiscoveryPreference(clientPackageName);
         updateAllRoutesFromManager();
 
         // Only used by non-system MediaRouter2.
@@ -1060,11 +1062,48 @@
                 .build();
     }
 
+    private List<MediaRoute2Info> getSortedRoutes(List<MediaRoute2Info> routes,
+            RouteDiscoveryPreference preference) {
+        if (!preference.shouldRemoveDuplicates()) {
+            return routes;
+        }
+        Map<String, Integer> packagePriority = new ArrayMap<>();
+        int count = preference.getDeduplicationPackageOrder().size();
+        for (int i = 0; i < count; i++) {
+            // the last package will have 1 as the priority
+            packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+        }
+        ArrayList<MediaRoute2Info> sortedRoutes = new ArrayList<>(routes);
+        // take the negative for descending order
+        sortedRoutes.sort(Comparator.comparingInt(
+                r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+        return sortedRoutes;
+    }
+
     private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
-            RouteDiscoveryPreference discoveryRequest) {
-        return routes.stream()
-                .filter(route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
-                .collect(Collectors.toList());
+            RouteDiscoveryPreference discoveryPreference) {
+
+        Set<String> deduplicationIdSet = new ArraySet<>();
+
+        List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+        for (MediaRoute2Info route : getSortedRoutes(routes, discoveryPreference)) {
+            if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+                    || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+                continue;
+            }
+            if (!discoveryPreference.getAllowedPackages().isEmpty()
+                    && !discoveryPreference.getAllowedPackages().contains(route.getPackageName())) {
+                continue;
+            }
+            if (discoveryPreference.shouldRemoveDuplicates()) {
+                if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
+                    continue;
+                }
+                deduplicationIdSet.addAll(route.getDeduplicationIds());
+            }
+            filteredRoutes.add(route);
+        }
+        return filteredRoutes;
     }
 
     private void updateAllRoutesFromManager() {
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 83fa7c2..8635c0e 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -29,6 +29,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -36,15 +38,18 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
@@ -84,7 +89,8 @@
     @GuardedBy("mRoutesLock")
     private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
     @NonNull
-    final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
+    final ConcurrentMap<String, RouteDiscoveryPreference> mDiscoveryPreferenceMap =
+            new ConcurrentHashMap<>();
 
     private final AtomicInteger mNextRequestId = new AtomicInteger(1);
     private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
@@ -247,25 +253,8 @@
      */
     @NonNull
     public List<MediaRoute2Info> getAvailableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
-        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
-        List<MediaRoute2Info> routes = new ArrayList<>();
-
-        String packageName = sessionInfo.getClientPackageName();
-        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
-        if (preferredFeatures == null) {
-            preferredFeatures = Collections.emptyList();
-        }
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : mRoutes.values()) {
-                if (route.hasAnyFeatures(preferredFeatures)
-                        || sessionInfo.getSelectedRoutes().contains(route.getId())
-                        || sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                    routes.add(route);
-                }
-            }
-        }
-        return routes;
+        return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/true,
+                null);
     }
 
     /**
@@ -281,27 +270,70 @@
      */
     @NonNull
     public List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
+        return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/false,
+                (route) -> sessionInfo.isSystemSession() ^ route.isSystemRoute());
+    }
+
+    private List<MediaRoute2Info> getSortedRoutes(RouteDiscoveryPreference preference) {
+        if (!preference.shouldRemoveDuplicates()) {
+            synchronized (mRoutesLock) {
+                return List.copyOf(mRoutes.values());
+            }
+        }
+        Map<String, Integer> packagePriority = new ArrayMap<>();
+        int count = preference.getDeduplicationPackageOrder().size();
+        for (int i = 0; i < count; i++) {
+            // the last package will have 1 as the priority
+            packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+        }
+        ArrayList<MediaRoute2Info> routes;
+        synchronized (mRoutesLock) {
+            routes = new ArrayList<>(mRoutes.values());
+        }
+        // take the negative for descending order
+        routes.sort(Comparator.comparingInt(
+                r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+        return routes;
+    }
+
+    private List<MediaRoute2Info> getFilteredRoutes(@NonNull RoutingSessionInfo sessionInfo,
+            boolean includeSelectedRoutes,
+            @Nullable Predicate<MediaRoute2Info> additionalFilter) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
         List<MediaRoute2Info> routes = new ArrayList<>();
 
+        Set<String> deduplicationIdSet = new ArraySet<>();
         String packageName = sessionInfo.getClientPackageName();
-        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
-        if (preferredFeatures == null) {
-            preferredFeatures = Collections.emptyList();
-        }
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : mRoutes.values()) {
-                if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                    routes.add(route);
+        RouteDiscoveryPreference discoveryPreference =
+                mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
+
+        for (MediaRoute2Info route : getSortedRoutes(discoveryPreference)) {
+            if (sessionInfo.getTransferableRoutes().contains(route.getId())
+                    || (includeSelectedRoutes
+                    && sessionInfo.getSelectedRoutes().contains(route.getId()))) {
+                routes.add(route);
+                continue;
+            }
+            if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+                    || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+                continue;
+            }
+            if (!discoveryPreference.getAllowedPackages().isEmpty()
+                    && !discoveryPreference.getAllowedPackages()
+                    .contains(route.getPackageName())) {
+                continue;
+            }
+            if (additionalFilter != null && !additionalFilter.test(route)) {
+                continue;
+            }
+            if (discoveryPreference.shouldRemoveDuplicates()) {
+                if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
                     continue;
                 }
-                // Add Phone -> Cast and Cast -> Phone
-                if (route.hasAnyFeatures(preferredFeatures)
-                        && (sessionInfo.isSystemSession() ^ route.isSystemRoute())) {
-                    routes.add(route);
-                }
+                deduplicationIdSet.addAll(route.getDeduplicationIds());
             }
+            routes.add(route);
         }
         return routes;
     }
@@ -310,44 +342,10 @@
      * Returns the preferred features of the specified package name.
      */
     @NonNull
-    public List<String> getPreferredFeatures(@NonNull String packageName) {
+    public RouteDiscoveryPreference getDiscoveryPreference(@NonNull String packageName) {
         Objects.requireNonNull(packageName, "packageName must not be null");
 
-        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
-        if (preferredFeatures == null) {
-            preferredFeatures = Collections.emptyList();
-        }
-        return preferredFeatures;
-    }
-
-    /**
-     * Returns a list of routes which are related to the given package name in the given route list.
-     */
-    @NonNull
-    public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes,
-            @NonNull String packageName) {
-        Objects.requireNonNull(routes, "routes must not be null");
-        Objects.requireNonNull(packageName, "packageName must not be null");
-
-        List<RoutingSessionInfo> sessions = getRoutingSessions(packageName);
-        RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1);
-
-        List<MediaRoute2Info> result = new ArrayList<>();
-        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
-        if (preferredFeatures == null) {
-            preferredFeatures = Collections.emptyList();
-        }
-
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : routes) {
-                if (route.hasAnyFeatures(preferredFeatures)
-                        || sessionInfo.getSelectedRoutes().contains(route.getId())
-                        || sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                    result.add(route);
-                }
-            }
-        }
-        return result;
+        return mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
     }
 
     /**
@@ -713,19 +711,19 @@
         }
     }
 
-    void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
-        if (preferredFeatures == null) {
-            mPreferredFeaturesMap.remove(packageName);
+    void updateDiscoveryPreference(String packageName, RouteDiscoveryPreference preference) {
+        if (preference == null) {
+            mDiscoveryPreferenceMap.remove(packageName);
             return;
         }
-        List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
-        if ((prevFeatures == null && preferredFeatures.size() == 0)
-                || Objects.equals(preferredFeatures, prevFeatures)) {
+        RouteDiscoveryPreference prevPreference =
+                mDiscoveryPreferenceMap.put(packageName, preference);
+        if (Objects.equals(preference, prevPreference)) {
             return;
         }
         for (CallbackRecord record : mCallbackRecords) {
             record.mExecutor.execute(() -> record.mCallback
-                    .onPreferredFeaturesChanged(packageName, preferredFeatures));
+                    .onDiscoveryPreferenceChanged(packageName, preference));
         }
     }
 
@@ -1047,6 +1045,17 @@
                 @NonNull List<String> preferredFeatures) {}
 
         /**
+         * Called when the preferred route features of an app is changed.
+         *
+         * @param packageName the package name of the application
+         * @param discoveryPreference the new discovery preference set by the application.
+         */
+        default void onDiscoveryPreferenceChanged(@NonNull String packageName,
+                @NonNull RouteDiscoveryPreference discoveryPreference) {
+            onPreferredFeaturesChanged(packageName, discoveryPreference.getPreferredFeatures());
+        }
+
+        /**
          * Called when a previous request has failed.
          *
          * @param reason the reason that the request has failed. Can be one of followings:
@@ -1125,9 +1134,10 @@
         }
 
         @Override
-        public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
-                    MediaRouter2Manager.this, packageName, features));
+        public void notifyDiscoveryPreferenceChanged(String packageName,
+                RouteDiscoveryPreference discoveryPreference) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateDiscoveryPreference,
+                    MediaRouter2Manager.this, packageName, discoveryPreference));
         }
 
         @Override
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 37fee84..0045018 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -64,6 +64,13 @@
 
     @NonNull
     private final List<String> mPreferredFeatures;
+    @NonNull
+    private final List<String> mRequiredFeatures;
+    @NonNull
+    private final List<String> mPackagesOrder;
+    @NonNull
+    private final List<String> mAllowedPackages;
+
     private final boolean mShouldPerformActiveScan;
     @Nullable
     private final Bundle mExtras;
@@ -78,12 +85,18 @@
 
     RouteDiscoveryPreference(@NonNull Builder builder) {
         mPreferredFeatures = builder.mPreferredFeatures;
+        mRequiredFeatures = builder.mRequiredFeatures;
+        mPackagesOrder = builder.mPackageOrder;
+        mAllowedPackages = builder.mAllowedPackages;
         mShouldPerformActiveScan = builder.mActiveScan;
         mExtras = builder.mExtras;
     }
 
     RouteDiscoveryPreference(@NonNull Parcel in) {
         mPreferredFeatures = in.createStringArrayList();
+        mRequiredFeatures = in.createStringArrayList();
+        mPackagesOrder = in.createStringArrayList();
+        mAllowedPackages = in.createStringArrayList();
         mShouldPerformActiveScan = in.readBoolean();
         mExtras = in.readBundle();
     }
@@ -96,6 +109,8 @@
      * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
      * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
      * </p>
+     *
+     * @see #getRequiredFeatures()
      */
     @NonNull
     public List<String> getPreferredFeatures() {
@@ -103,6 +118,47 @@
     }
 
     /**
+     * Gets the required features of routes that media router would like to discover.
+     * <p>
+     * Routes that have all the required features will be discovered.
+     * They may include predefined features such as
+     * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+     * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
+     *
+     * @see #getPreferredFeatures()
+     */
+    @NonNull
+    public List<String> getRequiredFeatures() {
+        return mRequiredFeatures;
+    }
+
+    /**
+     * Gets the ordered list of package names used to remove duplicate routes.
+     * <p>
+     * Duplicate route removal is enabled if the returned list is non-empty. Routes are deduplicated
+     * based on their {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}. If two routes
+     * have a deduplication ID in common, only the route from the provider whose package name is
+     * first in the provided list will remain.
+     *
+     * @see #shouldRemoveDuplicates()
+     */
+    @NonNull
+    public List<String> getDeduplicationPackageOrder() {
+        return mPackagesOrder;
+    }
+
+    /**
+     * Gets the list of allowed packages.
+     * <p>
+     * If it's not empty, it will only discover routes from the provider whose package name
+     * belongs to the list.
+     */
+    @NonNull
+    public List<String> getAllowedPackages() {
+        return mAllowedPackages;
+    }
+
+    /**
      * Gets whether active scanning should be performed.
      * <p>
      * If any of discovery preferences sets this as {@code true}, active scanning will
@@ -114,6 +170,15 @@
     }
 
     /**
+     * Gets whether duplicate routes removal is enabled.
+     *
+     * @see #getDeduplicationPackageOrder()
+     */
+    public boolean shouldRemoveDuplicates() {
+        return !mPackagesOrder.isEmpty();
+    }
+
+    /**
      * @hide
      */
     public Bundle getExtras() {
@@ -128,6 +193,9 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStringList(mPreferredFeatures);
+        dest.writeStringList(mRequiredFeatures);
+        dest.writeStringList(mPackagesOrder);
+        dest.writeStringList(mAllowedPackages);
         dest.writeBoolean(mShouldPerformActiveScan);
         dest.writeBundle(mExtras);
     }
@@ -155,14 +223,17 @@
             return false;
         }
         RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
-        //TODO: Make this order-free
         return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+                && Objects.equals(mRequiredFeatures, other.mRequiredFeatures)
+                && Objects.equals(mPackagesOrder, other.mPackagesOrder)
+                && Objects.equals(mAllowedPackages, other.mAllowedPackages)
                 && mShouldPerformActiveScan == other.mShouldPerformActiveScan;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan);
+        return Objects.hash(mPreferredFeatures, mRequiredFeatures, mPackagesOrder, mAllowedPackages,
+                mShouldPerformActiveScan);
     }
 
     /**
@@ -170,13 +241,21 @@
      */
     public static final class Builder {
         List<String> mPreferredFeatures;
+        List<String> mRequiredFeatures;
+        List<String> mPackageOrder;
+        List<String> mAllowedPackages;
+
         boolean mActiveScan;
+
         Bundle mExtras;
 
         public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
             Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
             mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
                     .collect(Collectors.toList());
+            mRequiredFeatures = List.of();
+            mPackageOrder = List.of();
+            mAllowedPackages = List.of();
             mActiveScan = activeScan;
         }
 
@@ -184,12 +263,15 @@
             Objects.requireNonNull(preference, "preference must not be null");
 
             mPreferredFeatures = preference.getPreferredFeatures();
+            mRequiredFeatures = preference.getRequiredFeatures();
+            mPackageOrder = preference.getDeduplicationPackageOrder();
+            mAllowedPackages = preference.getAllowedPackages();
             mActiveScan = preference.shouldPerformActiveScan();
             mExtras = preference.getExtras();
         }
 
         /**
-         * A constructor to combine all of the preferences into a single preference.
+         * A constructor to combine all the preferences into a single preference.
          * It ignores extras of preferences.
          *
          * @hide
@@ -224,6 +306,30 @@
         }
 
         /**
+         * Sets the required route features to discover.
+         */
+        @NonNull
+        public Builder setRequiredFeatures(@NonNull List<String> requiredFeatures) {
+            Objects.requireNonNull(requiredFeatures, "preferredFeatures must not be null");
+            mRequiredFeatures = requiredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+                    .collect(Collectors.toList());
+            return this;
+        }
+
+        /**
+         * Sets the list of package names of providers that media router would like to discover.
+         * <p>
+         * If it's non-empty, media router only discovers route from the provider in the list.
+         * The default value is empty, which discovers routes from all providers.
+         */
+        @NonNull
+        public Builder setAllowedPackages(@NonNull List<String> allowedPackages) {
+            Objects.requireNonNull(allowedPackages, "allowedPackages must not be null");
+            mAllowedPackages = List.copyOf(allowedPackages);
+            return this;
+        }
+
+        /**
          * Sets if active scanning should be performed.
          * <p>
          * Since active scanning uses more system resources, set this as {@code true} only
@@ -237,6 +343,24 @@
         }
 
         /**
+         * Sets the order of packages to use when removing duplicate routes.
+         * <p>
+         * Routes are deduplicated based on their
+         * {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}.
+         * If two routes have a deduplication ID in common, only the route from the provider whose
+         * package name is first in the provided list will remain.
+         *
+         * @param packageOrder ordered list of package names used to remove duplicate routes, or an
+         *                     empty list if deduplication should not be enabled.
+         */
+        @NonNull
+        public Builder setDeduplicationPackageOrder(@NonNull List<String> packageOrder) {
+            Objects.requireNonNull(packageOrder, "packageOrder must not be null");
+            mPackageOrder = List.copyOf(packageOrder);
+            return this;
+        }
+
+        /**
          * Sets the extras of the route.
          * @hide
          */
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index aa076e8..fd8a06d 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -401,10 +401,14 @@
 
 ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
         const uint8_t *data, size_t numBytes, int64_t timestamp) {
-    if (inputPort == nullptr || data == nullptr) {
+    if (inputPort == nullptr || data == nullptr || numBytes < 0 || timestamp < 0) {
         return AMEDIA_ERROR_INVALID_PARAMETER;
     }
 
+    if (numBytes == 0) {
+        return 0;
+    }
+
     // AMIDI_logBuffer(data, numBytes);
 
     uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35c794e..b7beb6e 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -309,24 +309,27 @@
     android_res_nquery; # introduced=29
     android_res_nresult; # introduced=29
     android_res_nsend; # introduced=29
+    android_tag_socket_with_uid; # introduced=Tiramisu
+    android_tag_socket; # introduced=Tiramisu
+    android_untag_socket; # introduced=Tiramisu
     AThermal_acquireManager; # introduced=30
     AThermal_releaseManager; # introduced=30
     AThermal_getCurrentThermalStatus; # introduced=30
     AThermal_registerThermalStatusListener; # introduced=30
     AThermal_unregisterThermalStatusListener; # introduced=30
     AThermal_getThermalHeadroom; # introduced=31
+    APerformanceHint_getManager; # introduced=Tiramisu
+    APerformanceHint_createSession; # introduced=Tiramisu
+    APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu
+    APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
+    APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
+    APerformanceHint_closeSession; # introduced=Tiramisu
   local:
     *;
 };
 
 LIBANDROID_PLATFORM {
   global:
-    APerformanceHint_getManager;
-    APerformanceHint_createSession;
-    APerformanceHint_getPreferredUpdateRateNanos;
-    APerformanceHint_updateTargetWorkDuration;
-    APerformanceHint_reportActualWorkDuration;
-    APerformanceHint_closeSession;
     APerformanceHint_setIHintManagerForTesting;
     extern "C++" {
         ASurfaceControl_registerSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 51a0c99..0c36051 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,17 +16,18 @@
 
 #define LOG_TAG "perf_hint"
 
-#include <utility>
-#include <vector>
-
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
 #include <binder/IServiceManager.h>
 #include <performance_hint_private.h>
 #include <utils/SystemClock.h>
 
+#include <utility>
+#include <vector>
+
 using namespace android;
 using namespace android::os;
 
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 284e9ee..b17850e 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -18,10 +18,12 @@
 
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
 #include <binder/IBinder.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <performance_hint_private.h>
+
 #include <memory>
 #include <vector>
 
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index a0f3098..bb25274 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -198,14 +198,16 @@
             return kGray_8_SkColorType;
         case ANDROID_BITMAP_FORMAT_RGBA_F16:
             return kRGBA_F16_SkColorType;
+        case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+            return kRGBA_1010102_SkColorType;
         default:
             return kUnknown_SkColorType;
     }
 }
 
 int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) {
-    if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE
-            || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
+    if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE ||
+        format > ANDROID_BITMAP_FORMAT_RGBA_1010102) {
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
 
@@ -290,6 +292,8 @@
             return ANDROID_BITMAP_FORMAT_A_8;
         case kRGBA_F16_SkColorType:
             return ANDROID_BITMAP_FORMAT_RGBA_F16;
+        case kRGBA_1010102_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGBA_1010102;
         default:
             return ANDROID_BITMAP_FORMAT_NONE;
     }
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 223bdcdd..54538d9 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -39,7 +39,6 @@
         "src/android/net/TrafficStats.java",
         "src/android/net/UnderlyingNetworkInfo.*",
         "src/android/net/netstats/**/*.*",
-        "src/com/android/server/NetworkManagementSocketTagger.java",
     ],
     path: "src",
     visibility: [
@@ -126,14 +125,14 @@
     name: "framework-connectivity-ethernet-sources",
     srcs: [
         "src/android/net/EthernetManager.java",
+        "src/android/net/EthernetNetworkManagementException.java",
+        "src/android/net/EthernetNetworkManagementException.aidl",
         "src/android/net/EthernetNetworkSpecifier.java",
+        "src/android/net/EthernetNetworkUpdateRequest.java",
+        "src/android/net/EthernetNetworkUpdateRequest.aidl",
         "src/android/net/IEthernetManager.aidl",
+        "src/android/net/IEthernetNetworkManagementListener.aidl",
         "src/android/net/IEthernetServiceListener.aidl",
-        "src/android/net/IInternalNetworkManagementListener.aidl",
-        "src/android/net/InternalNetworkUpdateRequest.java",
-        "src/android/net/InternalNetworkUpdateRequest.aidl",
-        "src/android/net/InternalNetworkManagementException.java",
-        "src/android/net/InternalNetworkManagementException.aidl",
         "src/android/net/ITetheredInterfaceCallback.aidl",
     ],
     path: "src",
@@ -176,3 +175,34 @@
         "//packages/modules/Connectivity:__subpackages__",
     ],
 }
+
+cc_library_shared {
+    name: "libframework-connectivity-tiramisu-jni",
+    min_sdk_version: "30",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        // Don't warn about S API usage even with
+        // min_sdk 30: the library is only loaded
+        // on S+ devices
+        "-Wno-unguarded-availability",
+        "-Wthread-safety",
+    ],
+    srcs: [
+        "jni/android_net_TrafficStats.cpp",
+        "jni/onload.cpp",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    static_libs: [
+        "libnativehelper_compat_libc++",
+    ],
+    stl: "none",
+    apex_available: [
+        "com.android.tethering",
+        // TODO: remove when ConnectivityT moves to APEX.
+        "//apex_available:platform",
+    ],
+}
diff --git a/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp
new file mode 100644
index 0000000..f3c58b1
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp
@@ -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.
+ */
+
+#include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, jint tag, jint uid) {
+  int fd = AFileDescriptor_getFd(env, fileDescriptor);
+  if (fd == -1) return -EBADF;
+  return android_tag_socket_with_uid(fd, tag, uid);
+}
+
+static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
+  int fd = AFileDescriptor_getFd(env, fileDescriptor);
+  if (fd == -1) return -EBADF;
+  return android_untag_socket(fd);
+}
+
+static const JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*) tagSocketFd },
+    { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*) untagSocketFd },
+};
+
+int register_android_net_TrafficStats(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
+}
+
+};  // namespace android
+
diff --git a/packages/ConnectivityT/framework-t/jni/onload.cpp b/packages/ConnectivityT/framework-t/jni/onload.cpp
new file mode 100644
index 0000000..1fb42c6
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/jni/onload.cpp
@@ -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.
+ */
+
+#define LOG_TAG "FrameworkConnectivityJNI"
+
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+int register_android_net_TrafficStats(JNIEnv* env);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv *env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
+        return JNI_ERR;
+    }
+
+    if (register_android_net_TrafficStats(env) < 0) return JNI_ERR;
+
+    return JNI_VERSION_1_6;
+}
+
+};  // namespace android
+
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 84adef5..5ce7e59 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -41,6 +41,7 @@
 import android.net.NetworkTemplate;
 import android.net.UnderlyingNetworkInfo;
 import android.net.netstats.IUsageCallback;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
 import android.os.Build;
@@ -126,17 +127,12 @@
     private final INetworkStatsService mService;
 
     /**
-     * Type constants for reading different types of Data Usage.
+     * @deprecated Use {@link NetworkStatsDataMigrationUtils#PREFIX_XT}
+     * instead.
      * @hide
      */
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @Deprecated
     public static final String PREFIX_DEV = "dev";
-    /** @hide */
-    public static final String PREFIX_XT = "xt";
-    /** @hide */
-    public static final String PREFIX_UID = "uid";
-    /** @hide */
-    public static final String PREFIX_UID_TAG = "uid_tag";
 
     /** @hide */
     public static final int FLAG_POLL_ON_OPEN = 1 << 0;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index ece54df..f472d56 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -320,15 +320,15 @@
     }
 
     private static final class InternalNetworkManagementListener
-            extends IInternalNetworkManagementListener.Stub {
+            extends IEthernetNetworkManagementListener.Stub {
         @NonNull
         private final Executor mExecutor;
         @NonNull
-        private final BiConsumer<Network, InternalNetworkManagementException> mListener;
+        private final BiConsumer<Network, EthernetNetworkManagementException> mListener;
 
         InternalNetworkManagementListener(
                 @NonNull final Executor executor,
-                @NonNull final BiConsumer<Network, InternalNetworkManagementException> listener) {
+                @NonNull final BiConsumer<Network, EthernetNetworkManagementException> listener) {
             Objects.requireNonNull(executor, "Pass a non-null executor");
             Objects.requireNonNull(listener, "Pass a non-null listener");
             mExecutor = executor;
@@ -338,14 +338,14 @@
         @Override
         public void onComplete(
                 @Nullable final Network network,
-                @Nullable final InternalNetworkManagementException e) {
+                @Nullable final EthernetNetworkManagementException e) {
             mExecutor.execute(() -> mListener.accept(network, e));
         }
     }
 
     private InternalNetworkManagementListener getInternalNetworkManagementListener(
             @Nullable final Executor executor,
-            @Nullable final BiConsumer<Network, InternalNetworkManagementException> listener) {
+            @Nullable final BiConsumer<Network, EthernetNetworkManagementException> listener) {
         if (null != listener) {
             Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener");
         }
@@ -360,9 +360,9 @@
 
     private void updateConfiguration(
             @NonNull String iface,
-            @NonNull InternalNetworkUpdateRequest request,
+            @NonNull EthernetNetworkUpdateRequest request,
             @Nullable @CallbackExecutor Executor executor,
-            @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+            @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
         final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
                 executor, listener);
         try {
@@ -375,7 +375,7 @@
     private void connectNetwork(
             @NonNull String iface,
             @Nullable @CallbackExecutor Executor executor,
-            @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+            @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
         final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
                 executor, listener);
         try {
@@ -388,7 +388,7 @@
     private void disconnectNetwork(
             @NonNull String iface,
             @Nullable @CallbackExecutor Executor executor,
-            @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+            @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
         final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
                 executor, listener);
         try {
diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl
similarity index 93%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl
index dcce706..adf9e5a 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl
@@ -16,4 +16,4 @@
 
  package android.net;
 
- parcelable InternalNetworkManagementException;
\ No newline at end of file
+ parcelable EthernetNetworkManagementException;
\ No newline at end of file
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java
new file mode 100644
index 0000000..a35f28e
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/** @hide */
+public final class EthernetNetworkManagementException
+        extends RuntimeException implements Parcelable {
+
+    /* @hide */
+    public EthernetNetworkManagementException(@NonNull final String errorMessage) {
+        super(errorMessage);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getMessage());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        final EthernetNetworkManagementException that = (EthernetNetworkManagementException) obj;
+
+        return Objects.equals(getMessage(), that.getMessage());
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getMessage());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<EthernetNetworkManagementException> CREATOR =
+            new Parcelable.Creator<EthernetNetworkManagementException>() {
+                @Override
+                public EthernetNetworkManagementException[] newArray(int size) {
+                    return new EthernetNetworkManagementException[size];
+                }
+
+                @Override
+                public EthernetNetworkManagementException createFromParcel(@NonNull Parcel source) {
+                    return new EthernetNetworkManagementException(source.readString());
+                }
+            };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
similarity index 93%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
index da00cb9..debc348 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
@@ -16,4 +16,4 @@
 
  package android.net;
 
- parcelable InternalNetworkUpdateRequest;
\ No newline at end of file
+ parcelable EthernetNetworkUpdateRequest;
\ No newline at end of file
diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
similarity index 80%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
index f42c4b7..4d229d2 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
@@ -23,7 +23,7 @@
 import java.util.Objects;
 
 /** @hide */
-public final class InternalNetworkUpdateRequest implements Parcelable {
+public final class EthernetNetworkUpdateRequest implements Parcelable {
     @NonNull
     private final StaticIpConfiguration mIpConfig;
     @NonNull
@@ -40,7 +40,7 @@
     }
 
     /** @hide */
-    public InternalNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig,
+    public EthernetNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig,
             @NonNull final NetworkCapabilities networkCapabilities) {
         Objects.requireNonNull(ipConfig);
         Objects.requireNonNull(networkCapabilities);
@@ -48,7 +48,7 @@
         mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
     }
 
-    private InternalNetworkUpdateRequest(@NonNull final Parcel source) {
+    private EthernetNetworkUpdateRequest(@NonNull final Parcel source) {
         Objects.requireNonNull(source);
         mIpConfig = StaticIpConfiguration.CREATOR.createFromParcel(source);
         mNetworkCapabilities = NetworkCapabilities.CREATOR.createFromParcel(source);
@@ -56,7 +56,7 @@
 
     @Override
     public String toString() {
-        return "InternalNetworkUpdateRequest{"
+        return "EthernetNetworkUpdateRequest{"
                 + "mIpConfig=" + mIpConfig
                 + ", mNetworkCapabilities=" + mNetworkCapabilities + '}';
     }
@@ -65,7 +65,7 @@
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        InternalNetworkUpdateRequest that = (InternalNetworkUpdateRequest) o;
+        EthernetNetworkUpdateRequest that = (EthernetNetworkUpdateRequest) o;
 
         return Objects.equals(that.getIpConfig(), mIpConfig)
                 && Objects.equals(that.getNetworkCapabilities(), mNetworkCapabilities);
@@ -88,16 +88,16 @@
     }
 
     @NonNull
-    public static final Parcelable.Creator<InternalNetworkUpdateRequest> CREATOR =
-            new Parcelable.Creator<InternalNetworkUpdateRequest>() {
+    public static final Parcelable.Creator<EthernetNetworkUpdateRequest> CREATOR =
+            new Parcelable.Creator<EthernetNetworkUpdateRequest>() {
                 @Override
-                public InternalNetworkUpdateRequest[] newArray(int size) {
-                    return new InternalNetworkUpdateRequest[size];
+                public EthernetNetworkUpdateRequest[] newArray(int size) {
+                    return new EthernetNetworkUpdateRequest[size];
                 }
 
                 @Override
-                public InternalNetworkUpdateRequest createFromParcel(@NonNull Parcel source) {
-                    return new InternalNetworkUpdateRequest(source);
+                public EthernetNetworkUpdateRequest createFromParcel(@NonNull Parcel source) {
+                    return new EthernetNetworkUpdateRequest(source);
                 }
             };
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
index e688bea..544d02b 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
@@ -18,8 +18,8 @@
 
 import android.net.IpConfiguration;
 import android.net.IEthernetServiceListener;
-import android.net.IInternalNetworkManagementListener;
-import android.net.InternalNetworkUpdateRequest;
+import android.net.IEthernetNetworkManagementListener;
+import android.net.EthernetNetworkUpdateRequest;
 import android.net.ITetheredInterfaceCallback;
 
 /**
@@ -38,8 +38,8 @@
     void setIncludeTestInterfaces(boolean include);
     void requestTetheredInterface(in ITetheredInterfaceCallback callback);
     void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
-    void updateConfiguration(String iface, in InternalNetworkUpdateRequest request,
-        in IInternalNetworkManagementListener listener);
-    void connectNetwork(String iface, in IInternalNetworkManagementListener listener);
-    void disconnectNetwork(String iface, in IInternalNetworkManagementListener listener);
+    void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request,
+        in IEthernetNetworkManagementListener listener);
+    void connectNetwork(String iface, in IEthernetNetworkManagementListener listener);
+    void disconnectNetwork(String iface, in IEthernetNetworkManagementListener listener);
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
similarity index 80%
rename from packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
index 69cde3b..93edccf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
@@ -16,10 +16,10 @@
 
 package android.net;
 
-import android.net.InternalNetworkManagementException;
+import android.net.EthernetNetworkManagementException;
 import android.net.Network;
 
 /** @hide */
-oneway interface IInternalNetworkManagementListener {
-    void onComplete(in Network network, in InternalNetworkManagementException exception);
+oneway interface IEthernetNetworkManagementListener {
+    void onComplete(in Network network, in EthernetNetworkManagementException exception);
 }
\ No newline at end of file
diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java
deleted file mode 100644
index 7f4e403..0000000
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java
+++ /dev/null
@@ -1,59 +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 android.net;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/** @hide */
-public final class InternalNetworkManagementException
-        extends RuntimeException implements Parcelable {
-
-    /* @hide */
-    public InternalNetworkManagementException(@NonNull final Throwable t) {
-        super(t);
-    }
-
-    private InternalNetworkManagementException(@NonNull final Parcel source) {
-        super(source.readString());
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString(getCause().getMessage());
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @NonNull
-    public static final Parcelable.Creator<InternalNetworkManagementException> CREATOR =
-            new Parcelable.Creator<InternalNetworkManagementException>() {
-                @Override
-                public InternalNetworkManagementException[] newArray(int size) {
-                    return new InternalNetworkManagementException[size];
-                }
-
-                @Override
-                public InternalNetworkManagementException createFromParcel(@NonNull Parcel source) {
-                    return new InternalNetworkManagementException(source);
-                }
-            };
-}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index c2f0cdf..bc836d8 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -31,12 +31,9 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
+import android.os.StrictMode;
 import android.util.Log;
 
-import com.android.server.NetworkManagementSocketTagger;
-
-import dalvik.system.SocketTagger;
-
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramSocket;
@@ -56,6 +53,10 @@
  * use {@link NetworkStatsManager} instead.
  */
 public class TrafficStats {
+    static {
+        System.loadLibrary("framework-connectivity-tiramisu-jni");
+    }
+
     private static final String TAG = TrafficStats.class.getSimpleName();
     /**
      * The return value to indicate that the device does not support the statistic.
@@ -232,9 +233,68 @@
      */
     @SystemApi(client = MODULE_LIBRARIES)
     public static void attachSocketTagger() {
-        NetworkManagementSocketTagger.install();
+        dalvik.system.SocketTagger.set(new SocketTagger());
     }
 
+    private static class SocketTagger extends dalvik.system.SocketTagger {
+
+        // TODO: set to false
+        private static final boolean LOGD = true;
+
+        SocketTagger() {
+        }
+
+        @Override
+        public void tag(FileDescriptor fd) throws SocketException {
+            final UidTag tagInfo = sThreadUidTag.get();
+            if (LOGD) {
+                Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
+                        + Integer.toHexString(tagInfo.tag) + ", statsUid=" + tagInfo.uid);
+            }
+            if (tagInfo.tag == -1) {
+                StrictMode.noteUntaggedSocket();
+            }
+
+            if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+            final int errno = native_tagSocketFd(fd, tagInfo.tag, tagInfo.uid);
+            if (errno < 0) {
+                Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
+                        + tagInfo.tag + ", "
+                        + tagInfo.uid + ") failed with errno" + errno);
+            }
+        }
+
+        @Override
+        public void untag(FileDescriptor fd) throws SocketException {
+            if (LOGD) {
+                Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
+            }
+
+            final UidTag tagInfo = sThreadUidTag.get();
+            if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+
+            final int errno = native_untagSocketFd(fd);
+            if (errno < 0) {
+                Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
+            }
+        }
+    }
+
+    private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
+    private static native int native_untagSocketFd(FileDescriptor fd);
+
+    private static class UidTag {
+        public int tag = -1;
+        public int uid = -1;
+    }
+
+    private static ThreadLocal<UidTag> sThreadUidTag = new ThreadLocal<UidTag>() {
+        @Override
+        protected UidTag initialValue() {
+            return new UidTag();
+        }
+    };
+
     /**
      * Set active tag to use when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
@@ -249,7 +309,7 @@
      * @see #clearThreadStatsTag()
      */
     public static void setThreadStatsTag(int tag) {
-        NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+        getAndSetThreadStatsTag(tag);
     }
 
     /**
@@ -267,7 +327,9 @@
      *         restore any existing values after a nested operation is finished
      */
     public static int getAndSetThreadStatsTag(int tag) {
-        return NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+        final int old = sThreadUidTag.get().tag;
+        sThreadUidTag.get().tag = tag;
+        return old;
     }
 
     /**
@@ -327,7 +389,7 @@
      * @see #setThreadStatsTag(int)
      */
     public static int getThreadStatsTag() {
-        return NetworkManagementSocketTagger.getThreadSocketStatsTag();
+        return sThreadUidTag.get().tag;
     }
 
     /**
@@ -337,7 +399,7 @@
      * @see #setThreadStatsTag(int)
      */
     public static void clearThreadStatsTag() {
-        NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
+        sThreadUidTag.get().tag = -1;
     }
 
     /**
@@ -357,7 +419,7 @@
      */
     @SuppressLint("RequiresPermission")
     public static void setThreadStatsUid(int uid) {
-        NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
+        sThreadUidTag.get().uid = uid;
     }
 
     /**
@@ -368,7 +430,7 @@
      * @see #setThreadStatsUid(int)
      */
     public static int getThreadStatsUid() {
-        return NetworkManagementSocketTagger.getThreadSocketStatsUid();
+        return sThreadUidTag.get().uid;
     }
 
     /**
@@ -395,7 +457,7 @@
      */
     @SuppressLint("RequiresPermission")
     public static void clearThreadStatsUid() {
-        NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
+        setThreadStatsUid(-1);
     }
 
     /**
diff --git a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
deleted file mode 100644
index 8bb12a6d..0000000
--- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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;
-
-import android.os.StrictMode;
-import android.util.Log;
-
-import dalvik.system.SocketTagger;
-
-import java.io.FileDescriptor;
-import java.net.SocketException;
-
-/**
- * Assigns tags to sockets for traffic stats.
- * @hide
- */
-public final class NetworkManagementSocketTagger extends SocketTagger {
-    private static final String TAG = "NetworkManagementSocketTagger";
-    private static final boolean LOGD = false;
-
-    private static ThreadLocal<SocketTags> threadSocketTags = new ThreadLocal<SocketTags>() {
-        @Override
-        protected SocketTags initialValue() {
-            return new SocketTags();
-        }
-    };
-
-    public static void install() {
-        SocketTagger.set(new NetworkManagementSocketTagger());
-    }
-
-    public static int setThreadSocketStatsTag(int tag) {
-        final int old = threadSocketTags.get().statsTag;
-        threadSocketTags.get().statsTag = tag;
-        return old;
-    }
-
-    public static int getThreadSocketStatsTag() {
-        return threadSocketTags.get().statsTag;
-    }
-
-    public static int setThreadSocketStatsUid(int uid) {
-        final int old = threadSocketTags.get().statsUid;
-        threadSocketTags.get().statsUid = uid;
-        return old;
-    }
-
-    public static int getThreadSocketStatsUid() {
-        return threadSocketTags.get().statsUid;
-    }
-
-    @Override
-    public void tag(FileDescriptor fd) throws SocketException {
-        final SocketTags options = threadSocketTags.get();
-        if (LOGD) {
-            Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
-                    + Integer.toHexString(options.statsTag) + ", statsUid=" + options.statsUid);
-        }
-        if (options.statsTag == -1) {
-            StrictMode.noteUntaggedSocket();
-        }
-        // TODO: skip tagging when options would be no-op
-        tagSocketFd(fd, options.statsTag, options.statsUid);
-    }
-
-    private void tagSocketFd(FileDescriptor fd, int tag, int uid) {
-        if (tag == -1 && uid == -1) return;
-
-        final int errno = native_tagSocketFd(fd, tag, uid);
-        if (errno < 0) {
-            Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
-                    + tag + ", "
-                    + uid + ") failed with errno" + errno);
-        }
-    }
-
-    @Override
-    public void untag(FileDescriptor fd) throws SocketException {
-        if (LOGD) {
-            Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
-        }
-        unTagSocketFd(fd);
-    }
-
-    private void unTagSocketFd(FileDescriptor fd) {
-        final SocketTags options = threadSocketTags.get();
-        if (options.statsTag == -1 && options.statsUid == -1) return;
-
-        final int errno = native_untagSocketFd(fd);
-        if (errno < 0) {
-            Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
-        }
-    }
-
-    public static class SocketTags {
-        public int statsTag = -1;
-        public int statsUid = -1;
-    }
-
-    /**
-     * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
-     * format like {@code 0x7fffffff00000000}.
-     */
-    public static int kernelToTag(String string) {
-        int length = string.length();
-        if (length > 10) {
-            return Long.decode(string.substring(0, length - 8)).intValue();
-        } else {
-            return 0;
-        }
-    }
-
-    private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
-    private static native int native_untagSocketFd(FileDescriptor fd);
-}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index 17f3455..668d1cb 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -22,8 +22,6 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -470,6 +468,19 @@
     }
 
     /**
+     * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
+     * format like {@code 0x7fffffff00000000}.
+     */
+    public static int kernelToTag(String string) {
+        int length = string.length();
+        if (length > 10) {
+            return Long.decode(string.substring(0, length - 8)).intValue();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
      * Parse statistics from file into given {@link NetworkStats} object. Values
      * are expected to monotonically increase since device boot.
      */
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 748b0ae..9f3371b 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -20,9 +20,6 @@
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG;
-import static android.app.usage.NetworkStatsManager.PREFIX_XT;
 import static android.content.Intent.ACTION_SHUTDOWN;
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.ACTION_USER_REMOVED;
@@ -50,6 +47,9 @@
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.net.TrafficStats.UID_TETHERING;
 import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
 import static android.os.Trace.TRACE_TAG_NETWORK;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 1d0ae99..b65e976 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -37,7 +37,7 @@
 import android.widget.Button;
 
 import com.android.internal.app.AlertActivity;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -154,8 +154,8 @@
                         final PackageLite pkg = result.getResult();
                         params.setAppPackageName(pkg.getPackageName());
                         params.setInstallLocation(pkg.getInstallLocation());
-                        params.setSize(
-                                PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
+                        params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
+                                params.abiOverride));
                     }
                 } catch (IOException e) {
                     Log.e(LOG_TAG,
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index fcf2282..684f4de 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -50,6 +50,7 @@
         "SettingsLibSettingsTransition",
         "SettingsLibActivityEmbedding",
         "SettingsLibButtonPreference",
+        "setupdesign",
     ],
 
     // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index a347345..13f8a37 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -18,4 +18,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.settingslib">
 
+    <application>
+        <activity
+            android:name="com.android.settingslib.users.AvatarPickerActivity"
+            android:theme="@style/SudThemeGlifV2.DayNight"/>
+    </application>
+
 </manifest>
diff --git a/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
new file mode 100644
index 0000000..97aec74
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <stroke
+                android:width="2dp"
+                android:color="?android:attr/colorPrimary"/>
+        </shape>
+    </item>
+    <item
+        android:left="@dimen/avatar_picker_icon_inset"
+        android:right="@dimen/avatar_picker_icon_inset"
+        android:top="@dimen/avatar_picker_icon_inset"
+        android:bottom="@dimen/avatar_picker_icon_inset"
+        android:drawable="@drawable/ic_avatar_choose_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/avatar_selector.xml b/packages/SettingsLib/res/drawable/avatar_selector.xml
new file mode 100644
index 0000000..ccde597
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_selector.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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true">
+        <shape android:shape="oval">
+            <stroke
+                android:color="?android:attr/colorPrimary"
+                android:width="@dimen/avatar_picker_padding"/>
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
new file mode 100644
index 0000000..7033aae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <stroke
+                android:width="2dp"
+                android:color="?android:attr/colorPrimary"/>
+        </shape>
+    </item>
+    <item
+        android:left="@dimen/avatar_picker_icon_inset"
+        android:right="@dimen/avatar_picker_icon_inset"
+        android:top="@dimen/avatar_picker_icon_inset"
+        android:bottom="@dimen/avatar_picker_icon_inset"
+        android:drawable="@drawable/ic_avatar_take_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
new file mode 100644
index 0000000..0cc54b6
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        android:pathData="M5.85,17.1q1.275,-0.975 2.85,-1.538Q10.275,15 12,15q1.725,0 3.3,0.563 1.575,0.562 2.85,1.537 0.875,-1.025 1.363,-2.325Q20,13.475 20,12q0,-3.325 -2.337,-5.662Q15.325,4 12,4T6.338,6.338Q4,8.675 4,12q0,1.475 0.487,2.775 0.488,1.3 1.363,2.325zM12,13q-1.475,0 -2.488,-1.012Q8.5,10.975 8.5,9.5t1.012,-2.487Q10.525,6 12,6t2.488,1.013Q15.5,8.024 15.5,9.5t-1.012,2.488Q13.475,13 12,13zM12,22q-2.075,0 -3.9,-0.788 -1.825,-0.787 -3.175,-2.137 -1.35,-1.35 -2.137,-3.175Q2,14.075 2,12t0.788,-3.9q0.787,-1.825 2.137,-3.175 1.35,-1.35 3.175,-2.137Q9.925,2 12,2t3.9,0.788q1.825,0.787 3.175,2.137 1.35,1.35 2.137,3.175Q22,9.925 22,12t-0.788,3.9q-0.787,1.825 -2.137,3.175 -1.35,1.35 -3.175,2.137Q14.075,22 12,22zM12,20q1.325,0 2.5,-0.387 1.175,-0.388 2.15,-1.113 -0.975,-0.725 -2.15,-1.113Q13.325,17 12,17t-2.5,0.387q-1.175,0.388 -2.15,1.113 0.975,0.725 2.15,1.113Q10.675,20 12,20zM12,11q0.65,0 1.075,-0.425 0.425,-0.425 0.425,-1.075 0,-0.65 -0.425,-1.075Q12.65,8 12,8q-0.65,0 -1.075,0.425Q10.5,8.85 10.5,9.5q0,0.65 0.425,1.075Q11.35,11 12,11zM12,9.5zM12,18.5z"/>
+</vector>
+
diff --git a/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
new file mode 100644
index 0000000..b85fdc2
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.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"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
new file mode 100644
index 0000000..5c56276
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.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"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        android:pathData="M12,17.5q1.875,0 3.188,-1.313Q16.5,14.876 16.5,13q0,-1.875 -1.313,-3.188Q13.876,8.5 12,8.5q-1.875,0 -3.188,1.313Q7.5,11.124 7.5,13q0,1.875 1.313,3.188Q10.124,17.5 12,17.5zM4,21q-0.825,0 -1.413,-0.587Q2,19.825 2,19L2,7q0,-0.825 0.587,-1.412Q3.175,5 4,5h3.15L9,3h6l1.85,2L20,5q0.825,0 1.413,0.588Q22,6.175 22,7v12q0,0.825 -0.587,1.413Q20.825,21 20,21zM20,19L20,7L4,7v12zM4,19L4,7v12z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/avatar_item.xml b/packages/SettingsLib/res/layout/avatar_item.xml
new file mode 100644
index 0000000..c52f664
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_item.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.
+  -->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/avatar_image"
+    android:layout_height="@dimen/avatar_size_in_picker"
+    android:layout_width="@dimen/avatar_size_in_picker"
+    android:layout_margin="@dimen/avatar_picker_margin"
+    android:layout_gravity="center"
+    android:padding="@dimen/avatar_picker_padding"
+    android:background="@drawable/avatar_selector"/>
diff --git a/packages/SettingsLib/res/layout/avatar_picker.xml b/packages/SettingsLib/res/layout/avatar_picker.xml
new file mode 100644
index 0000000..2d40bd0
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_picker.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+<com.google.android.setupdesign.GlifLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/glif_layout"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:icon="@drawable/ic_account_circle_outline"
+    app:sucUsePartnerResource="true"
+    app:sucHeaderText="@string/avatar_picker_title">
+
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:gravity="center_horizontal"
+        style="@style/SudContentFrame">
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/avatar_grid"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1280dp-land/dimens.xml b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">104dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1440dp-land/dimens.xml b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">128dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1600dp-land/dimens.xml b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">148dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w480dp-port/dimens.xml b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
new file mode 100644
index 0000000..cab78d6
--- /dev/null
+++ b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <integer name="avatar_picker_columns">3</integer>
+    <dimen name="avatar_size_in_picker">112dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values-w600dp-port/dimens.xml b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">104dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w720dp-port/dimens.xml b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">128dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w840dp-port/dimens.xml b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">148dp</dimen>
+    <dimen name="avatar_picker_padding">8dp</dimen>
+    <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w960dp-land/dimens.xml b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
new file mode 100644
index 0000000..8403dba
--- /dev/null
+++ b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <integer name="avatar_picker_columns">4</integer>
+    <dimen name="avatar_size_in_picker">96dp</dimen>
+    <dimen name="avatar_picker_padding">6dp</dimen>
+    <dimen name="avatar_picker_margin">2dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 2b5e9cd..29a1831 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -647,4 +647,11 @@
         <item>disabled</item>
     </array>
 
+    <!-- Images offered as options in the avatar picker. If populated, the avatar_image_descriptions
+         array must also be populated with a content description for each image. -->
+    <array name="avatar_images"/>
+
+    <!-- Content descriptions for each of the images in the avatar_images array. -->
+    <string-array name="avatar_image_descriptions"/>
+
 </resources>
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index b150e01..45253bb 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -28,9 +28,4 @@
     <!-- Control whether status bar should distinguish HSPA data icon form UMTS
     data icon on devices -->
     <bool name="config_hspa_data_distinguishable">false</bool>
-
-    <integer-array name="config_supportedDreamComplications">
-    </integer-array>
-    <integer-array name="config_dreamComplicationsEnabledByDefault">
-    </integer-array>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 9ee42b6..120df76 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -102,4 +102,11 @@
     <dimen name="user_photo_size_in_profile_info_dialog">112dp</dimen>
     <dimen name="add_a_photo_icon_size_in_profile_info_dialog">32dp</dimen>
 
+    <integer name="avatar_picker_columns">3</integer>
+    <dimen name="avatar_size_in_picker">96dp</dimen>
+    <dimen name="avatar_picker_padding">6dp</dimen>
+    <dimen name="avatar_picker_margin">2dp</dimen>
+
+    <dimen name="avatar_picker_icon_inset">25dp</dimen>
+
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5ada028..0fe869f 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1548,4 +1548,22 @@
 
     <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_no_calling">No calling.</string>
+
+    <!-- Screensaver overlay which displays the time. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_time">Time</string>
+    <!-- Screensaver overlay which displays the date. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_date">Date</string>
+    <!-- Screensaver overlay which displays the weather. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_weather">Weather</string>
+    <!-- Screensaver overlay which displays air quality. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_aqi">Air Quality</string>
+    <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_cast_info">Cast Info</string>
+
+    <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
+    <string name="avatar_picker_title">Choose a profile picture</string>
+
+    <!-- Content description for a default user icon. [CHAR LIMIT=NONE] -->
+    <string name="default_user_icon_description">Default user icon</string>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 3f322d6..f7b2974 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -16,8 +16,11 @@
 
 package com.android.settingslib;
 
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.os.Build;
@@ -102,8 +105,11 @@
         if (mDisabledSummary) {
             final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
             if (summaryView != null) {
-                final CharSequence disabledText = summaryView.getContext().getText(
-                        R.string.disabled_by_admin_summary_text);
+                final CharSequence disabledText = mContext
+                        .getSystemService(DevicePolicyManager.class)
+                        .getString(CONTROLLED_BY_ADMIN_SUMMARY,
+                                () -> summaryView.getContext().getString(
+                                        R.string.disabled_by_admin_summary_text));
                 if (mDisabledByAdmin) {
                     summaryView.setText(disabledText);
                 } else if (mDisabledByAppOps) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index d73e45e..883e080 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,7 +1,10 @@
 package com.android.settingslib;
 
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+
 import android.annotation.ColorInt;
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -124,7 +127,8 @@
         String name = info != null ? info.name : null;
         if (info.isManagedProfile()) {
             // We use predefined values for managed profiles
-            return context.getString(R.string.managed_user_title);
+            return context.getSystemService(DevicePolicyManager.class).getString(
+                    WORK_PROFILE_USER_LABEL, () -> context.getString(R.string.managed_user_title));
         } else if (info.isGuest()) {
             name = context.getString(R.string.user_guest);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 99e3160..15ca8cd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -173,8 +173,10 @@
     }
 
     public BluetoothDevice getActiveDevice() {
-        if (mService == null) return null;
-        return mService.getActiveDevice();
+        if (mBluetoothAdapter == null) return null;
+        final List<BluetoothDevice> activeDevices = mBluetoothAdapter
+                .getActiveDevices(BluetoothProfile.A2DP);
+        return (activeDevices.size() > 0) ? activeDevices.get(0) : null;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index b11bbde..7e5c124 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -132,10 +132,12 @@
     }
 
     public BluetoothDevice getActiveDevice() {
-        if (mService == null) {
+        if (mBluetoothAdapter == null) {
             return null;
         }
-        return mService.getActiveDevice();
+        final List<BluetoothDevice> activeDevices = mBluetoothAdapter
+                .getActiveDevices(BluetoothProfile.HEADSET);
+        return (activeDevices.size() > 0) ? activeDevices.get(0) : null;
     }
 
     public int getAudioState(BluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index dc109ca..6f2d4de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -173,8 +173,10 @@
     }
 
     public List<BluetoothDevice> getActiveDevices() {
-        if (mService == null) return new ArrayList<>();
-        return mService.getActiveDevices();
+        if (mBluetoothAdapter == null) {
+            return new ArrayList<>();
+        }
+        return mBluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 209507a..db6d41e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -21,12 +21,12 @@
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
-import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
@@ -177,10 +177,10 @@
     }
 
     public List<BluetoothDevice> getActiveDevices() {
-        if (mService == null) {
+        if (mBluetoothAdapter == null) {
             return new ArrayList<>();
         }
-        return mService.getActiveDevices();
+        return mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 46e31ce..a000c09 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -34,6 +34,7 @@
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
@@ -50,6 +51,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -149,13 +151,13 @@
                 .map(ComponentName::unflattenFromString)
                 .collect(Collectors.toSet());
 
-        mSupportedComplications =
-                Arrays.stream(resources.getIntArray(R.array.config_supportedDreamComplications))
-                        .boxed()
-                        .collect(Collectors.toSet());
+        mSupportedComplications = Arrays.stream(resources.getIntArray(
+                        com.android.internal.R.array.config_supportedDreamComplications))
+                .boxed()
+                .collect(Collectors.toSet());
 
-        mDefaultEnabledComplications = Arrays.stream(
-                        resources.getIntArray(R.array.config_dreamComplicationsEnabledByDefault))
+        mDefaultEnabledComplications = Arrays.stream(resources.getIntArray(
+                        com.android.internal.R.array.config_dreamComplicationsEnabledByDefault))
                 .boxed()
                 // A complication can only be enabled by default if it is also supported.
                 .filter(mSupportedComplications::contains)
@@ -292,6 +294,11 @@
         }
     }
 
+    /** Returns whether a particular complication is enabled */
+    public boolean isComplicationEnabled(@ComplicationType int complication) {
+        return getEnabledComplications().contains(complication);
+    }
+
     /** Gets all complications which have been enabled by the user. */
     public Set<Integer> getEnabledComplications() {
         final String enabledComplications = Settings.Secure.getString(
@@ -331,6 +338,35 @@
                 convertToString(enabledComplications));
     }
 
+    /**
+     * Gets the title of a particular complication type to be displayed to the user. If there
+     * is no title, null is returned.
+     */
+    @Nullable
+    public CharSequence getComplicationTitle(@ComplicationType int complicationType) {
+        int res = 0;
+        switch (complicationType) {
+            case COMPLICATION_TYPE_TIME:
+                res = R.string.dream_complication_title_time;
+                break;
+            case COMPLICATION_TYPE_DATE:
+                res = R.string.dream_complication_title_date;
+                break;
+            case COMPLICATION_TYPE_WEATHER:
+                res = R.string.dream_complication_title_weather;
+                break;
+            case COMPLICATION_TYPE_AIR_QUALITY:
+                res = R.string.dream_complication_title_aqi;
+                break;
+            case COMPLICATION_TYPE_CAST_INFO:
+                res = R.string.dream_complication_title_cast_info;
+                break;
+            default:
+                return null;
+        }
+        return mContext.getString(res);
+    }
+
     private static String convertToString(Set<Integer> set) {
         return set.stream()
                 .map(String::valueOf)
@@ -338,6 +374,9 @@
     }
 
     private static Set<Integer> parseFromString(String string) {
+        if (TextUtils.isEmpty(string)) {
+            return new HashSet<>();
+        }
         return Arrays.stream(string.split(","))
                 .map(Integer::parseInt)
                 .collect(Collectors.toSet());
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
new file mode 100644
index 0000000..61b8911
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.users;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.StrictMode;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.util.EventLog;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class AvatarPhotoController {
+    private static final String TAG = "AvatarPhotoController";
+
+    private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+    private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+    private static final int REQUEST_CODE_CROP_PHOTO = 1003;
+    // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
+    // so we need a default photo size
+    private static final int DEFAULT_PHOTO_SIZE = 500;
+
+    private static final String IMAGES_DIR = "multi_user";
+    private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
+    private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
+
+    private final int mPhotoSize;
+
+    private final AvatarPickerActivity mActivity;
+    private final String mFileAuthority;
+
+    private final File mImagesDir;
+    private final Uri mCropPictureUri;
+    private final Uri mTakePictureUri;
+
+    AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
+        mActivity = activity;
+        mFileAuthority = fileAuthority;
+
+        mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+        mImagesDir.mkdir();
+        mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
+        mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
+        mPhotoSize = getPhotoSize(activity);
+    }
+
+    /**
+     * Handles activity result from containing activity/fragment after a take/choose/crop photo
+     * action result is received.
+     */
+    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode != Activity.RESULT_OK) {
+            return false;
+        }
+        final Uri pictureUri = data != null && data.getData() != null
+                ? data.getData() : mTakePictureUri;
+
+        // Check if the result is a content uri
+        if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
+            Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
+            EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
+            return false;
+        }
+
+        switch (requestCode) {
+            case REQUEST_CODE_CROP_PHOTO:
+                mActivity.returnUriResult(pictureUri);
+                return true;
+            case REQUEST_CODE_TAKE_PHOTO:
+            case REQUEST_CODE_CHOOSE_PHOTO:
+                if (mTakePictureUri.equals(pictureUri)) {
+                    if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
+                        cropPhoto();
+                    } else {
+                        onPhotoNotCropped(pictureUri);
+                    }
+                } else {
+                    copyAndCropPhoto(pictureUri);
+                }
+                return true;
+        }
+        return false;
+    }
+
+    void takePhoto() {
+        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
+        appendOutputExtra(intent, mTakePictureUri);
+        mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+    }
+
+    void choosePhoto() {
+        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
+        intent.setType("image/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+    }
+
+    private void copyAndCropPhoto(final Uri pictureUri) {
+        // TODO: Replace AsyncTask
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                final ContentResolver cr = mActivity.getContentResolver();
+                try (InputStream in = cr.openInputStream(pictureUri);
+                     OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+                    Streams.copy(in, out);
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to copy photo", e);
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void result) {
+                if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
+                    cropPhoto();
+                }
+            }
+        }.execute();
+    }
+
+    private void cropPhoto() {
+        // TODO: Use a public intent, when there is one.
+        Intent intent = new Intent("com.android.camera.action.CROP");
+        intent.setDataAndType(mTakePictureUri, "image/*");
+        appendOutputExtra(intent, mCropPictureUri);
+        appendCropExtras(intent);
+        if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
+            try {
+                StrictMode.disableDeathOnFileUriExposure();
+                mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+            } finally {
+                StrictMode.enableDeathOnFileUriExposure();
+            }
+        } else {
+            onPhotoNotCropped(mTakePictureUri);
+        }
+    }
+
+    private void appendOutputExtra(Intent intent, Uri pictureUri) {
+        intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
+        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
+    }
+
+    private void appendCropExtras(Intent intent) {
+        intent.putExtra("crop", "true");
+        intent.putExtra("scale", true);
+        intent.putExtra("scaleUpIfNeeded", true);
+        intent.putExtra("aspectX", 1);
+        intent.putExtra("aspectY", 1);
+        intent.putExtra("outputX", mPhotoSize);
+        intent.putExtra("outputY", mPhotoSize);
+    }
+
+    private void onPhotoNotCropped(final Uri data) {
+        // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
+        new AsyncTask<Void, Void, Bitmap>() {
+            @Override
+            protected Bitmap doInBackground(Void... params) {
+                // Scale and crop to a square aspect ratio
+                Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
+                        Bitmap.Config.ARGB_8888);
+                Canvas canvas = new Canvas(croppedImage);
+                Bitmap fullImage;
+                try {
+                    InputStream imageStream = mActivity.getContentResolver()
+                            .openInputStream(data);
+                    fullImage = BitmapFactory.decodeStream(imageStream);
+                } catch (FileNotFoundException fe) {
+                    return null;
+                }
+                if (fullImage != null) {
+                    int rotation = getRotation(mActivity, data);
+                    final int squareSize = Math.min(fullImage.getWidth(),
+                            fullImage.getHeight());
+                    final int left = (fullImage.getWidth() - squareSize) / 2;
+                    final int top = (fullImage.getHeight() - squareSize) / 2;
+
+                    Matrix matrix = new Matrix();
+                    RectF rectSource = new RectF(left, top,
+                            left + squareSize, top + squareSize);
+                    RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
+                    matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
+                    matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
+                    canvas.drawBitmap(fullImage, matrix, new Paint());
+                    return croppedImage;
+                } else {
+                    // Bah! Got nothin.
+                    return null;
+                }
+            }
+
+            @Override
+            protected void onPostExecute(Bitmap bitmap) {
+                saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
+                mActivity.returnUriResult(mCropPictureUri);
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+    }
+
+    /**
+     * Reads the image's exif data and determines the rotation degree needed to display the image
+     * in portrait mode.
+     */
+    private int getRotation(Context context, Uri selectedImage) {
+        int rotation = -1;
+        try {
+            InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+            ExifInterface exif = new ExifInterface(imageStream);
+            rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
+        } catch (IOException exception) {
+            Log.e(TAG, "Error while getting rotation", exception);
+        }
+
+        switch (rotation) {
+            case ExifInterface.ORIENTATION_ROTATE_90:
+                return 90;
+            case ExifInterface.ORIENTATION_ROTATE_180:
+                return 180;
+            case ExifInterface.ORIENTATION_ROTATE_270:
+                return 270;
+            default:
+                return 0;
+        }
+    }
+
+    private void saveBitmapToFile(Bitmap bitmap, File file) {
+        try {
+            OutputStream os = new FileOutputStream(file);
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+            os.flush();
+            os.close();
+        } catch (IOException e) {
+            Log.e(TAG, "Cannot create temp file", e);
+        }
+    }
+
+    private static int getPhotoSize(Context context) {
+        try (Cursor cursor = context.getContentResolver().query(
+                ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+                new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
+            if (cursor != null) {
+                cursor.moveToFirst();
+                return cursor.getInt(0);
+            } else {
+                return DEFAULT_PHOTO_SIZE;
+            }
+        }
+    }
+
+    private Uri createTempImageUri(Context context, String fileName, boolean purge) {
+        final File fullPath = new File(mImagesDir, fileName);
+        if (purge) {
+            fullPath.delete();
+        }
+        return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
new file mode 100644
index 0000000..93be66a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.users;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.R;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.util.ThemeHelper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Activity to allow the user to choose a user profile picture.
+ *
+ * <p>Options are provided to take a photo or choose a photo using the photo picker. In addition,
+ * preselected avatar images may be provided in the resource array {@code avatar_images}. If
+ * provided, every element of that array must be a bitmap drawable.
+ *
+ * <p>If preselected images are not provided, the default avatar will be shown instead, in a range
+ * of colors.
+ *
+ * <p>This activity should be started with startActivityForResult. If a photo or a preselected image
+ * is selected, a Uri will be returned in the data field of the result intent. If a colored default
+ * avatar is selected, the chosen color will be returned as {@code EXTRA_DEFAULT_ICON_TINT_COLOR}
+ * and the data field will be empty.
+ */
+public class AvatarPickerActivity extends Activity {
+
+    static final String EXTRA_FILE_AUTHORITY = "file_authority";
+    static final String EXTRA_DEFAULT_ICON_TINT_COLOR = "default_icon_tint_color";
+
+    private static final String KEY_AWAITING_RESULT = "awaiting_result";
+    private static final String KEY_SELECTED_POSITION = "selected_position";
+
+    private boolean mWaitingForActivityResult;
+
+    private FooterButton mDoneButton;
+    private AvatarAdapter mAdapter;
+
+    private AvatarPhotoController mAvatarPhotoController;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ThemeHelper.trySetDynamicColor(this);
+        setContentView(R.layout.avatar_picker);
+        setUpButtons();
+
+        RecyclerView recyclerView = findViewById(R.id.avatar_grid);
+        mAdapter = new AvatarAdapter();
+        recyclerView.setAdapter(mAdapter);
+        recyclerView.setLayoutManager(new GridLayoutManager(this,
+                getResources().getInteger(R.integer.avatar_picker_columns)));
+
+        restoreState(savedInstanceState);
+
+        mAvatarPhotoController = new AvatarPhotoController(
+                this, mWaitingForActivityResult, getFileAuthority());
+    }
+
+    private void setUpButtons() {
+        GlifLayout glifLayout = findViewById(R.id.glif_layout);
+        FooterBarMixin mixin = glifLayout.getMixin(FooterBarMixin.class);
+
+        FooterButton secondaryButton =
+                new FooterButton.Builder(this)
+                        .setText("Cancel")
+                        .setListener(view -> cancel())
+                        .build();
+
+        mDoneButton =
+                new FooterButton.Builder(this)
+                        .setText("Done")
+                        .setListener(view -> mAdapter.returnSelectionResult())
+                        .build();
+        mDoneButton.setEnabled(false);
+
+        mixin.setSecondaryButton(secondaryButton);
+        mixin.setPrimaryButton(mDoneButton);
+    }
+
+    private String getFileAuthority() {
+        String authority = getIntent().getStringExtra(EXTRA_FILE_AUTHORITY);
+        if (authority == null) {
+            throw new IllegalStateException("File authority must be provided");
+        }
+        return authority;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        mWaitingForActivityResult = false;
+        mAvatarPhotoController.onActivityResult(requestCode, resultCode, data);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
+        outState.putInt(KEY_SELECTED_POSITION, mAdapter.mSelectedPosition);
+        super.onSaveInstanceState(outState);
+    }
+
+    private void restoreState(Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false);
+            mAdapter.mSelectedPosition =
+                    savedInstanceState.getInt(KEY_SELECTED_POSITION, AvatarAdapter.NONE);
+        }
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode) {
+        mWaitingForActivityResult = true;
+        super.startActivityForResult(intent, requestCode);
+    }
+
+    void returnUriResult(Uri uri) {
+        Intent resultData = new Intent();
+        resultData.setData(uri);
+        setResult(RESULT_OK, resultData);
+        finish();
+    }
+
+    void returnColorResult(int color) {
+        Intent resultData = new Intent();
+        resultData.putExtra(EXTRA_DEFAULT_ICON_TINT_COLOR, color);
+        setResult(RESULT_OK, resultData);
+        finish();
+    }
+
+    private void cancel() {
+        setResult(RESULT_CANCELED);
+        finish();
+    }
+
+    private class AvatarAdapter extends RecyclerView.Adapter<AvatarViewHolder> {
+
+        private static final int NONE = -1;
+
+        private final int mTakePhotoPosition;
+        private final int mChoosePhotoPosition;
+        private final int mPreselectedImageStartPosition;
+
+        private final List<Drawable> mImageDrawables;
+        private final List<String> mImageDescriptions;
+        private final TypedArray mPreselectedImages;
+        private final int[] mUserIconColors;
+        private int mSelectedPosition = NONE;
+
+        AvatarAdapter() {
+            final boolean canTakePhoto =
+                    PhotoCapabilityUtils.canTakePhoto(AvatarPickerActivity.this);
+            final boolean canChoosePhoto =
+                    PhotoCapabilityUtils.canChoosePhoto(AvatarPickerActivity.this);
+            mTakePhotoPosition = (canTakePhoto ? 0 : NONE);
+            mChoosePhotoPosition = (canChoosePhoto ? (canTakePhoto ? 1 : 0) : NONE);
+            mPreselectedImageStartPosition = (canTakePhoto ? 1 : 0) + (canChoosePhoto ? 1 : 0);
+
+            mPreselectedImages = getResources().obtainTypedArray(R.array.avatar_images);
+            mUserIconColors = UserIcons.getUserIconColors(getResources());
+            mImageDrawables = buildDrawableList();
+            mImageDescriptions = buildDescriptionsList();
+        }
+
+        @NonNull
+        @Override
+        public AvatarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
+            LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+            View itemView = layoutInflater.inflate(R.layout.avatar_item, parent, false);
+            return new AvatarViewHolder(itemView);
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull AvatarViewHolder viewHolder, int position) {
+            if (position == mTakePhotoPosition) {
+                viewHolder.setDrawable(getDrawable(R.drawable.avatar_take_photo_circled));
+                viewHolder.setContentDescription(getString(R.string.user_image_take_photo));
+                viewHolder.setClickListener(view -> mAvatarPhotoController.takePhoto());
+
+            } else if (position == mChoosePhotoPosition) {
+                viewHolder.setDrawable(getDrawable(R.drawable.avatar_choose_photo_circled));
+                viewHolder.setContentDescription(getString(R.string.user_image_choose_photo));
+                viewHolder.setClickListener(view -> mAvatarPhotoController.choosePhoto());
+
+            } else if (position >= mPreselectedImageStartPosition) {
+                int index = indexFromPosition(position);
+                viewHolder.setSelected(position == mSelectedPosition);
+                viewHolder.setDrawable(mImageDrawables.get(index));
+                if (mImageDescriptions != null) {
+                    viewHolder.setContentDescription(mImageDescriptions.get(index));
+                } else {
+                    viewHolder.setContentDescription(
+                            getString(R.string.default_user_icon_description));
+                }
+                viewHolder.setClickListener(view -> {
+                    if (mSelectedPosition == position) {
+                        deselect(position);
+                    } else {
+                        select(position);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return mPreselectedImageStartPosition + mImageDrawables.size();
+        }
+
+        private List<Drawable> buildDrawableList() {
+            List<Drawable> result = new ArrayList<>();
+
+            for (int i = 0; i < mPreselectedImages.length(); i++) {
+                Drawable drawable = mPreselectedImages.getDrawable(i);
+                if (drawable instanceof BitmapDrawable) {
+                    result.add(circularDrawableFrom((BitmapDrawable) drawable));
+                } else {
+                    throw new IllegalStateException("Avatar drawables must be bitmaps");
+                }
+            }
+            if (!result.isEmpty()) {
+                return result;
+            }
+
+            // No preselected images. Use tinted default icon.
+            for (int i = 0; i < mUserIconColors.length; i++) {
+                result.add(UserIcons.getDefaultUserIconInColor(getResources(), mUserIconColors[i]));
+            }
+            return result;
+        }
+
+        private List<String> buildDescriptionsList() {
+            if (mPreselectedImages.length() > 0) {
+                return Arrays.asList(
+                        getResources().getStringArray(R.array.avatar_image_descriptions));
+            }
+
+            return null;
+        }
+
+        private Drawable circularDrawableFrom(BitmapDrawable drawable) {
+            Bitmap bitmap = drawable.getBitmap();
+
+            RoundedBitmapDrawable roundedBitmapDrawable =
+                    RoundedBitmapDrawableFactory.create(getResources(), bitmap);
+            roundedBitmapDrawable.setCircular(true);
+
+            return roundedBitmapDrawable;
+        }
+
+        private int indexFromPosition(int position) {
+            return position - mPreselectedImageStartPosition;
+        }
+
+        private void select(int position) {
+            final int oldSelection = mSelectedPosition;
+            mSelectedPosition = position;
+            notifyItemChanged(position);
+            if (oldSelection != NONE) {
+                notifyItemChanged(oldSelection);
+            } else {
+                mDoneButton.setEnabled(true);
+            }
+        }
+
+        private void deselect(int position) {
+            mSelectedPosition = NONE;
+            notifyItemChanged(position);
+            mDoneButton.setEnabled(false);
+        }
+
+        private void returnSelectionResult() {
+            int index = indexFromPosition(mSelectedPosition);
+            if (mPreselectedImages.length() > 0) {
+                int resourceId = mPreselectedImages.getResourceId(index, -1);
+                if (resourceId == -1) {
+                    throw new IllegalStateException("Preselected avatar images must be resources.");
+                }
+                returnUriResult(uriForResourceId(resourceId));
+            } else {
+                returnColorResult(
+                        mUserIconColors[index]);
+            }
+        }
+
+        private Uri uriForResourceId(int resourceId) {
+            return new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+                    .authority(getResources().getResourcePackageName(resourceId))
+                    .appendPath(getResources().getResourceTypeName(resourceId))
+                    .appendPath(getResources().getResourceEntryName(resourceId))
+                    .build();
+        }
+    }
+
+    private static class AvatarViewHolder extends RecyclerView.ViewHolder {
+        private final ImageView mImageView;
+
+        AvatarViewHolder(View view) {
+            super(view);
+            mImageView = view.findViewById(R.id.avatar_image);
+        }
+
+        public void setDrawable(Drawable drawable) {
+            mImageView.setImageDrawable(drawable);
+        }
+
+        public void setContentDescription(String desc) {
+            mImageView.setContentDescription(desc);
+        }
+
+        public void setClickListener(View.OnClickListener listener) {
+            mImageView.setOnClickListener(listener);
+        }
+
+        public void setSelected(boolean selected) {
+            mImageView.setSelected(selected);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 6204336..80ee86f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
@@ -36,6 +37,8 @@
 
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 
 import java.io.File;
@@ -139,12 +142,20 @@
         Drawable userIcon = getUserIcon(activity, defaultUserIcon);
         userPhotoView.setImageDrawable(userIcon);
 
-        if (canChangePhoto(activity)) {
-            mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
-                    userPhotoView);
-        } else {
-            // some users can't change their photos, so we need to remove the suggestive icon
+        if (isChangePhotoRestrictedByBase(activity)) {
+            // some users can't change their photos so we need to remove the suggestive icon
             content.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE);
+        } else {
+            RestrictedLockUtils.EnforcedAdmin adminRestriction =
+                    getChangePhotoAdminRestriction(activity);
+            if (adminRestriction != null) {
+                userPhotoView.setOnClickListener(view ->
+                        RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+                                activity, adminRestriction));
+            } else {
+                mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
+                        userPhotoView);
+            }
         }
 
         mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
@@ -203,16 +214,21 @@
     }
 
     @VisibleForTesting
-    boolean canChangePhoto(Context context) {
-        return (PhotoCapabilityUtils.canCropPhoto(context)
-                && PhotoCapabilityUtils.canChoosePhoto(context))
-                || PhotoCapabilityUtils.canTakePhoto(context);
+    boolean isChangePhotoRestrictedByBase(Context context) {
+        return RestrictedLockUtilsInternal.hasBaseUserRestriction(
+                context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
+    }
+
+    @VisibleForTesting
+    RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) {
+        return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+                context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
     }
 
     @VisibleForTesting
     EditUserPhotoController createEditUserPhotoController(Activity activity,
             ActivityStarter activityStarter, ImageView userPhotoView) {
         return new EditUserPhotoController(activity, activityStarter, userPhotoView,
-                mSavedPhoto, mWaitingForActivityResult, mFileAuthority);
+                mSavedPhoto, mFileAuthority);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index f9584a3..f8bb38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -16,46 +16,21 @@
 
 package com.android.settingslib.users;
 
+import android.annotation.NonNull;
 import android.app.Activity;
-import android.content.ClipData;
-import android.content.ContentResolver;
-import android.content.Context;
 import android.content.Intent;
-import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.media.ExifInterface;
 import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.StrictMode;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract.DisplayPhoto;
-import android.provider.MediaStore;
-import android.util.EventLog;
 import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
 import android.widget.ImageView;
-import android.widget.ListPopupWindow;
-import android.widget.TextView;
 
-import androidx.core.content.FileProvider;
-
+import com.android.internal.util.UserIcons;
 import com.android.settingslib.R;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.drawable.CircleFramedDrawable;
-
-import libcore.io.Streams;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -63,8 +38,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.ExecutionException;
 
 /**
  * This class contains logic for starting activities to take/choose/crop photo, reads and transforms
@@ -75,45 +49,30 @@
 
     // It seems that this class generates custom request codes and they may
     // collide with ours, these values are very unlikely to have a conflict.
-    private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
-    private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
-    private static final int REQUEST_CODE_CROP_PHOTO = 1003;
-    // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
-    // so we need a default photo size
-    private static final int DEFAULT_PHOTO_SIZE = 500;
+    private static final int REQUEST_CODE_PICK_AVATAR = 1004;
 
     private static final String IMAGES_DIR = "multi_user";
-    private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
-    private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
     private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
 
-    private final int mPhotoSize;
-
     private final Activity mActivity;
     private final ActivityStarter mActivityStarter;
     private final ImageView mImageView;
     private final String mFileAuthority;
 
     private final File mImagesDir;
-    private final Uri mCropPictureUri;
-    private final Uri mTakePictureUri;
-
     private Bitmap mNewUserPhotoBitmap;
     private Drawable mNewUserPhotoDrawable;
 
     public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
-            ImageView view, Bitmap bitmap, boolean waiting, String fileAuthority) {
+            ImageView view, Bitmap bitmap, String fileAuthority) {
         mActivity = activity;
         mActivityStarter = activityStarter;
-        mImageView = view;
         mFileAuthority = fileAuthority;
 
         mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
         mImagesDir.mkdir();
-        mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
-        mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
-        mPhotoSize = getPhotoSize(activity);
-        mImageView.setOnClickListener(v -> showUpdatePhotoPopup());
+        mImageView = view;
+        mImageView.setOnClickListener(v -> showAvatarPicker());
         mNewUserPhotoBitmap = bitmap;
     }
 
@@ -125,32 +84,19 @@
         if (resultCode != Activity.RESULT_OK) {
             return false;
         }
-        final Uri pictureUri = data != null && data.getData() != null
-                ? data.getData() : mTakePictureUri;
 
-        // Check if the result is a content uri
-        if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
-            Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
-            EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
-            return false;
-        }
+        if (requestCode == REQUEST_CODE_PICK_AVATAR) {
+            if (data.hasExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR)) {
+                int tintColor =
+                        data.getIntExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR, -1);
+                onDefaultIconSelected(tintColor);
+                return true;
+            }
+            if (data.getData() != null) {
+                onPhotoCropped(data.getData());
+                return true;
+            }
 
-        switch (requestCode) {
-            case REQUEST_CODE_CROP_PHOTO:
-                onPhotoCropped(pictureUri);
-                return true;
-            case REQUEST_CODE_TAKE_PHOTO:
-            case REQUEST_CODE_CHOOSE_PHOTO:
-                if (mTakePictureUri.equals(pictureUri)) {
-                    if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
-                        cropPhoto();
-                    } else {
-                        onPhotoNotCropped(pictureUri);
-                    }
-                } else {
-                    copyAndCropPhoto(pictureUri);
-                }
-                return true;
         }
         return false;
     }
@@ -159,224 +105,60 @@
         return mNewUserPhotoDrawable;
     }
 
-    private void showUpdatePhotoPopup() {
-        final Context context = mImageView.getContext();
-        final boolean canTakePhoto = PhotoCapabilityUtils.canTakePhoto(context);
-        final boolean canChoosePhoto = PhotoCapabilityUtils.canChoosePhoto(context);
-
-        if (!canTakePhoto && !canChoosePhoto) {
-            return;
-        }
-
-        final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>();
-
-        if (canTakePhoto) {
-            final String title = context.getString(R.string.user_image_take_photo);
-            items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
-                    this::takePhoto));
-        }
-
-        if (canChoosePhoto) {
-            final String title = context.getString(R.string.user_image_choose_photo);
-            items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
-                    this::choosePhoto));
-        }
-
-        final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
-
-        listPopupWindow.setAnchorView(mImageView);
-        listPopupWindow.setModal(true);
-        listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
-        listPopupWindow.setAdapter(new RestrictedPopupMenuAdapter(context, items));
-
-        final int width = Math.max(mImageView.getWidth(), context.getResources()
-                .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
-        listPopupWindow.setWidth(width);
-        listPopupWindow.setDropDownGravity(Gravity.START);
-
-        listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
-            listPopupWindow.dismiss();
-            final RestrictedMenuItem item =
-                    (RestrictedMenuItem) parent.getAdapter().getItem(position);
-            item.doAction();
-        });
-
-        listPopupWindow.show();
+    private void showAvatarPicker() {
+        Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+        intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
+        mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
     }
 
-    private void takePhoto() {
-        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
-        appendOutputExtra(intent, mTakePictureUri);
-        mActivityStarter.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
-    }
+    private void onDefaultIconSelected(int tintColor) {
+        try {
+            ThreadUtils.postOnBackgroundThread(() -> {
+                Drawable drawable =
+                        UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor);
+                Bitmap bitmap = convertToBitmap(drawable,
+                        (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size));
 
-    private void choosePhoto() {
-        Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
-        intent.setType("image/*");
-        appendOutputExtra(intent, mTakePictureUri);
-        mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
-    }
-
-    private void copyAndCropPhoto(final Uri pictureUri) {
-        // TODO: Replace AsyncTask
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                final ContentResolver cr = mActivity.getContentResolver();
-                try (InputStream in = cr.openInputStream(pictureUri);
-                     OutputStream out = cr.openOutputStream(mTakePictureUri)) {
-                    Streams.copy(in, out);
-                } catch (IOException e) {
-                    Log.w(TAG, "Failed to copy photo", e);
-                }
-                return null;
-            }
-
-            @Override
-            protected void onPostExecute(Void result) {
-                if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
-                    cropPhoto();
-                }
-            }
-        }.execute();
-    }
-
-    private void cropPhoto() {
-        // TODO: Use a public intent, when there is one.
-        Intent intent = new Intent("com.android.camera.action.CROP");
-        intent.setDataAndType(mTakePictureUri, "image/*");
-        appendOutputExtra(intent, mCropPictureUri);
-        appendCropExtras(intent);
-        if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
-            try {
-                StrictMode.disableDeathOnFileUriExposure();
-                mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
-            } finally {
-                StrictMode.enableDeathOnFileUriExposure();
-            }
-        } else {
-            onPhotoNotCropped(mTakePictureUri);
+                ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap));
+            }).get();
+        } catch (InterruptedException | ExecutionException e) {
+            Log.e(TAG, "Error processing default icon", e);
         }
     }
 
-    private void appendOutputExtra(Intent intent, Uri pictureUri) {
-        intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
-        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
-    }
-
-    private void appendCropExtras(Intent intent) {
-        intent.putExtra("crop", "true");
-        intent.putExtra("scale", true);
-        intent.putExtra("scaleUpIfNeeded", true);
-        intent.putExtra("aspectX", 1);
-        intent.putExtra("aspectY", 1);
-        intent.putExtra("outputX", mPhotoSize);
-        intent.putExtra("outputY", mPhotoSize);
+    private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) {
+        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        icon.setBounds(0, 0, size, size);
+        icon.draw(canvas);
+        return bitmap;
     }
 
     private void onPhotoCropped(final Uri data) {
-        // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
-        new AsyncTask<Void, Void, Bitmap>() {
-            @Override
-            protected Bitmap doInBackground(Void... params) {
-                InputStream imageStream = null;
-                try {
-                    imageStream = mActivity.getContentResolver()
-                            .openInputStream(data);
-                    return BitmapFactory.decodeStream(imageStream);
-                } catch (FileNotFoundException fe) {
-                    Log.w(TAG, "Cannot find image file", fe);
-                    return null;
-                } finally {
-                    if (imageStream != null) {
-                        try {
-                            imageStream.close();
-                        } catch (IOException ioe) {
-                            Log.w(TAG, "Cannot close image stream", ioe);
-                        }
+        ThreadUtils.postOnBackgroundThread(() -> {
+            InputStream imageStream = null;
+            Bitmap bitmap = null;
+            try {
+                imageStream = mActivity.getContentResolver()
+                        .openInputStream(data);
+                bitmap = BitmapFactory.decodeStream(imageStream);
+            } catch (FileNotFoundException fe) {
+                Log.w(TAG, "Cannot find image file", fe);
+            } finally {
+                if (imageStream != null) {
+                    try {
+                        imageStream.close();
+                    } catch (IOException ioe) {
+                        Log.w(TAG, "Cannot close image stream", ioe);
                     }
                 }
             }
 
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                onPhotoProcessed(bitmap);
-
+            if (bitmap != null) {
+                Bitmap finalBitmap = bitmap;
+                ThreadUtils.postOnMainThread(() -> onPhotoProcessed(finalBitmap));
             }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-    }
-
-    private void onPhotoNotCropped(final Uri data) {
-        // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
-        new AsyncTask<Void, Void, Bitmap>() {
-            @Override
-            protected Bitmap doInBackground(Void... params) {
-                // Scale and crop to a square aspect ratio
-                Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
-                        Config.ARGB_8888);
-                Canvas canvas = new Canvas(croppedImage);
-                Bitmap fullImage;
-                try {
-                    InputStream imageStream = mActivity.getContentResolver()
-                            .openInputStream(data);
-                    fullImage = BitmapFactory.decodeStream(imageStream);
-                } catch (FileNotFoundException fe) {
-                    return null;
-                }
-                if (fullImage != null) {
-                    int rotation = getRotation(mActivity, data);
-                    final int squareSize = Math.min(fullImage.getWidth(),
-                            fullImage.getHeight());
-                    final int left = (fullImage.getWidth() - squareSize) / 2;
-                    final int top = (fullImage.getHeight() - squareSize) / 2;
-
-                    Matrix matrix = new Matrix();
-                    RectF rectSource = new RectF(left, top,
-                            left + squareSize, top + squareSize);
-                    RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
-                    matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
-                    matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
-                    canvas.drawBitmap(fullImage, matrix, new Paint());
-                    return croppedImage;
-                } else {
-                    // Bah! Got nothin.
-                    return null;
-                }
-            }
-
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                onPhotoProcessed(bitmap);
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-    }
-
-    /**
-     * Reads the image's exif data and determines the rotation degree needed to display the image
-     * in portrait mode.
-     */
-    private int getRotation(Context context, Uri selectedImage) {
-        int rotation = -1;
-        try {
-            InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
-            ExifInterface exif = new ExifInterface(imageStream);
-            rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
-        } catch (IOException exception) {
-            Log.e(TAG, "Error while getting rotation", exception);
-        }
-
-        switch (rotation) {
-            case ExifInterface.ORIENTATION_ROTATE_90:
-                return 90;
-            case ExifInterface.ORIENTATION_ROTATE_180:
-                return 180;
-            case ExifInterface.ORIENTATION_ROTATE_270:
-                return 270;
-            default:
-                return 0;
-        }
+        });
     }
 
     private void onPhotoProcessed(Bitmap bitmap) {
@@ -386,29 +168,6 @@
                     .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
             mImageView.setImageDrawable(mNewUserPhotoDrawable);
         }
-        new File(mImagesDir, TAKE_PICTURE_FILE_NAME).delete();
-        new File(mImagesDir, CROP_PICTURE_FILE_NAME).delete();
-    }
-
-    private static int getPhotoSize(Context context) {
-        try (Cursor cursor = context.getContentResolver().query(
-                DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
-                new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
-            if (cursor != null) {
-                cursor.moveToFirst();
-                return cursor.getInt(0);
-            } else {
-                return DEFAULT_PHOTO_SIZE;
-            }
-        }
-    }
-
-    private Uri createTempImageUri(Context context, String fileName, boolean purge) {
-        final File fullPath = new File(mImagesDir, fileName);
-        if (purge) {
-            fullPath.delete();
-        }
-        return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
     }
 
     File saveNewUserPhotoBitmap() {
@@ -435,84 +194,4 @@
     void removeNewUserPhotoBitmapFile() {
         new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME).delete();
     }
-
-    private static final class RestrictedMenuItem {
-        private final Context mContext;
-        private final String mTitle;
-        private final Runnable mAction;
-        private final RestrictedLockUtils.EnforcedAdmin mAdmin;
-        // Restriction may be set by system or something else via UserManager.setUserRestriction().
-        private final boolean mIsRestrictedByBase;
-
-        /**
-         * The menu item, used for popup menu. Any element of such a menu can be disabled by admin.
-         *
-         * @param context     A context.
-         * @param title       The title of the menu item.
-         * @param restriction The restriction, that if is set, blocks the menu item.
-         * @param action      The action on menu item click.
-         */
-        RestrictedMenuItem(Context context, String title, String restriction,
-                Runnable action) {
-            mContext = context;
-            mTitle = title;
-            mAction = action;
-
-            final int myUserId = UserHandle.myUserId();
-            mAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
-                    restriction, myUserId);
-            mIsRestrictedByBase = RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
-                    restriction, myUserId);
-        }
-
-        @Override
-        public String toString() {
-            return mTitle;
-        }
-
-        void doAction() {
-            if (isRestrictedByBase()) {
-                return;
-            }
-
-            if (isRestrictedByAdmin()) {
-                RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mAdmin);
-                return;
-            }
-
-            mAction.run();
-        }
-
-        boolean isRestrictedByAdmin() {
-            return mAdmin != null;
-        }
-
-        boolean isRestrictedByBase() {
-            return mIsRestrictedByBase;
-        }
-    }
-
-    /**
-     * Provide this adapter to ListPopupWindow.setAdapter() to have a popup window menu, where
-     * any element can be restricted by admin (profile owner or device owner).
-     */
-    private static final class RestrictedPopupMenuAdapter extends ArrayAdapter<RestrictedMenuItem> {
-        RestrictedPopupMenuAdapter(Context context, List<RestrictedMenuItem> items) {
-            super(context, R.layout.restricted_popup_menu_item, R.id.text, items);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final View view = super.getView(position, convertView, parent);
-            final RestrictedMenuItem item = getItem(position);
-            final TextView text = (TextView) view.findViewById(R.id.text);
-            final ImageView image = (ImageView) view.findViewById(R.id.restricted_icon);
-
-            text.setEnabled(!item.isRestrictedByAdmin() && !item.isRestrictedByBase());
-            image.setVisibility(item.isRestrictedByAdmin() && !item.isRestrictedByBase()
-                    ? ImageView.VISIBLE : ImageView.GONE);
-
-            return view;
-        }
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
index 165c280..b8615a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
@@ -40,12 +40,12 @@
 
     /**
      * Check if the current user can perform any activity for
-     * android.intent.action.GET_CONTENT action for images.
+     * ACTION_PICK_IMAGES action for images.
      * Returns false if the device is currently locked and
      * requires a PIN, pattern or password to unlock.
      */
     public static boolean canChoosePhoto(Context context) {
-        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
         intent.setType("image/*");
         boolean canPerformActivityForGetImage =
                 context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index f167721..d7b366e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -60,6 +60,8 @@
     private BluetoothDevice mDevice;
     @Mock
     private BluetoothA2dp mBluetoothA2dp;
+    @Mock
+    private BluetoothAdapter mBluetoothAdapter;
     private BluetoothProfile.ServiceListener mServiceListener;
 
     private A2dpProfile mProfile;
@@ -72,7 +74,8 @@
         mProfile = new A2dpProfile(mContext, mDeviceManager, mProfileManager);
         mServiceListener = mShadowBluetoothAdapter.getServiceListener();
         mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp);
-        when(mBluetoothA2dp.getActiveDevice()).thenReturn(mDevice);
+        when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.A2DP)))
+                .thenReturn(Arrays.asList(mDevice));
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 53d4653..86f7850 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -24,8 +24,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 
-import com.android.settingslib.R;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -53,10 +51,15 @@
 
         final Resources res = mock(Resources.class);
         when(mContext.getResources()).thenReturn(res);
-        when(res.getIntArray(R.array.config_supportedDreamComplications)).thenReturn(
+        when(res.getIntArray(
+                com.android.internal.R.array.config_supportedDreamComplications)).thenReturn(
                 SUPPORTED_DREAM_COMPLICATIONS);
-        when(res.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)).thenReturn(
+        when(res.getIntArray(
+                com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)).thenReturn(
                 DEFAULT_DREAM_COMPLICATIONS);
+        when(res.getStringArray(
+                com.android.internal.R.array.config_disabledDreamComponents)).thenReturn(
+                new String[]{});
         mBackend = new DreamBackend(mContext);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index d6c8816..a5ee4c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -62,7 +62,7 @@
     @Mock
     private ActivityStarter mActivityStarter;
 
-    private boolean mCanChangePhoto;
+    private boolean mPhotoRestrictedByBase;
     private Activity mActivity;
     private TestEditUserInfoController mController;
 
@@ -85,8 +85,8 @@
         }
 
         @Override
-        boolean canChangePhoto(Context context) {
-            return mCanChangePhoto;
+        boolean isChangePhotoRestrictedByBase(Context context) {
+            return mPhotoRestrictedByBase;
         }
     }
 
@@ -96,7 +96,7 @@
         mActivity = spy(ActivityController.of(new FragmentActivity()).get());
         mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
         mController = new TestEditUserInfoController();
-        mCanChangePhoto = true;
+        mPhotoRestrictedByBase = true;
     }
 
     @Test
@@ -260,7 +260,7 @@
 
     @Test
     public void createDialog_canNotChangePhoto_nullPhotoController() {
-        mCanChangePhoto = false;
+        mPhotoRestrictedByBase = false;
 
         mController.createDialog(mActivity, mActivityStarter, mCurrentIcon,
                 "test", "title", null, null);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index dc7632d..b851232 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -46,7 +46,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.util.XmlUtils;
@@ -783,7 +783,7 @@
                         + " VALUES(?,?);");
                 loadSetting(stmt, Global.SET_INSTALL_LOCATION, 0);
                 loadSetting(stmt, Global.DEFAULT_INSTALL_LOCATION,
-                        PackageHelper.APP_INSTALL_AUTO);
+                        InstallLocationUtils.APP_INSTALL_AUTO);
                 db.setTransactionSuccessful();
              } finally {
                  db.endTransaction();
@@ -2534,7 +2534,7 @@
 
             loadSetting(stmt, Settings.Global.SET_INSTALL_LOCATION, 0);
             loadSetting(stmt, Settings.Global.DEFAULT_INSTALL_LOCATION,
-                    PackageHelper.APP_INSTALL_AUTO);
+                    InstallLocationUtils.APP_INSTALL_AUTO);
 
             // Set default cdma emergency tone
             loadSetting(stmt, Settings.Global.EMERGENCY_TONE, 0);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 27fc6ba..ca90fbe 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -192,6 +192,9 @@
     <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS" />
+    <!-- Permission required for processes that don't own the focused window to switch
+         touch mode state -->
+    <uses-permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE" />
     <!-- Permission required to test onPermissionsChangedListener -->
     <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
     <uses-permission android:name="android.permission.SET_KEYBOARD_LAYOUT" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f35f5dd..776a511 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -213,6 +213,9 @@
     <!-- DevicePolicyManager get user restrictions -->
     <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
 
+    <!-- DevicePolicyManager get admin policy -->
+    <uses-permission android:name="android.permission.QUERY_ADMIN_POLICY" />
+
     <!-- TV picture-in-picture -->
     <uses-permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE" />
 
@@ -301,6 +304,11 @@
 
     <!-- For clipboard overlay -->
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+    <uses-permission android:name="android.permission.SET_CLIP_SOURCE" />
+
+    <!-- To change system language (HDMI CEC) -->
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION" />
 
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
@@ -485,6 +493,16 @@
             android:excludeFromRecents="true">
         </activity>
 
+        <!-- started from HdmiCecLocalDevicePlayback -->
+        <activity android:name=".hdmi.HdmiCecSetMenuLanguageActivity"
+                  android:exported="true"
+                  android:launchMode="singleTop"
+                  android:permission="android.permission.CHANGE_CONFIGURATION"
+                  android:theme="@style/BottomSheet"
+                  android:finishOnCloseSystemDialogs="true"
+                  android:excludeFromRecents="true">
+        </activity>
+
         <!-- started from SensoryPrivacyService -->
         <activity android:name=".sensorprivacy.SensorUseStartedActivity"
                   android:exported="true"
@@ -855,11 +873,11 @@
             android:singleUser="true"
             android:permission="android.permission.BIND_DREAM_SERVICE" />
 
-        <!-- Service for external clients to do media transfer -->
-        <!-- TODO(b/203800643): Export and guard with a permission. -->
+        <!-- Service for external clients to notify us of nearby media devices -->
+        <!-- TODO(b/216313420): Export and guard with a permission. -->
         <service
-            android:name=".media.taptotransfer.sender.MediaTttSenderService"
-           />
+            android:name=".media.nearby.NearbyMediaDevicesService"
+            />
 
         <receiver
             android:name=".tuner.TunerService$ClearReceiver"
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index dee4ff5..9722b1f 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
   // Looking for unit test presubmit configuration?
   // This currently lives in ATP config apct/system_ui/unit_test
-  "presubmit": [
+  "presubmit-large": [
     {
       "name": "PlatformScenarioTests",
       "options": [
@@ -24,7 +24,9 @@
             "exclude-annotation": "android.platform.test.scenario.annotation.FoldableOnly"
         }
       ]
-    },
+    }
+  ],
+  "presubmit": [
     {
       "name": "SystemUIGoogleTests",
       "options": [
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index ecb3cb3..339cab4 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -19,8 +19,10 @@
 <com.android.systemui.qs.FooterActionsView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="48dp"
-    android:gravity="center_vertical">
+    android:layout_height="@dimen/qs_footer_height"
+    android:gravity="center_vertical"
+    android:layout_gravity="bottom"
+>
 
     <com.android.systemui.statusbar.phone.MultiUserSwitch
         android:id="@+id/multi_user_switch"
diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
new file mode 100644
index 0000000..95bdd89
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+-->
+
+<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
+<com.android.systemui.qs.FooterActionsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/qs_footer_height"
+    android:gravity="center_vertical"
+    android:layout_gravity="bottom"
+>
+
+    <View
+        android:layout_height="1dp"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        />
+
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+        >
+
+        <com.android.systemui.statusbar.phone.MultiUserSwitch
+            android:id="@+id/multi_user_switch"
+            android:layout_width="@dimen/qs_footer_action_button_size"
+            android:layout_height="@dimen/qs_footer_action_button_size"
+            android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+            android:background="@drawable/qs_footer_action_circle"
+            android:focusable="true">
+
+            <ImageView
+                android:id="@+id/multi_user_avatar"
+                android:layout_width="@dimen/multi_user_avatar_expanded_size"
+                android:layout_height="@dimen/multi_user_avatar_expanded_size"
+                android:layout_gravity="center"
+                android:scaleType="centerInside" />
+        </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+            android:id="@+id/settings_button_container"
+            android:layout_width="@dimen/qs_footer_action_button_size"
+            android:layout_height="@dimen/qs_footer_action_button_size"
+            android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+            android:background="@drawable/qs_footer_action_circle"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+
+            <com.android.systemui.statusbar.phone.SettingsButton
+                android:id="@+id/settings_button"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/qs_footer_action_button_size"
+                android:layout_gravity="center"
+                android:background="@android:color/transparent"
+                android:contentDescription="@string/accessibility_quick_settings_settings"
+                android:padding="@dimen/qs_footer_icon_padding"
+                android:scaleType="centerInside"
+                android:src="@drawable/ic_settings"
+                android:tint="?android:attr/textColorPrimary" />
+
+            <com.android.systemui.statusbar.AlphaOptimizedImageView
+                android:id="@+id/tuner_icon"
+                android:layout_width="8dp"
+                android:layout_height="8dp"
+                android:layout_gravity="center_horizontal|bottom"
+                android:layout_marginBottom="@dimen/qs_footer_icon_padding"
+                android:src="@drawable/tuner"
+                android:tint="?android:attr/textColorTertiary"
+                android:visibility="invisible" />
+
+        </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/pm_lite"
+            android:layout_width="@dimen/qs_footer_action_button_size"
+            android:layout_height="@dimen/qs_footer_action_button_size"
+            android:background="@drawable/qs_footer_action_circle_color"
+            android:clickable="true"
+            android:clipToPadding="false"
+            android:focusable="true"
+            android:padding="@dimen/qs_footer_icon_padding"
+            android:src="@*android:drawable/ic_lock_power_off"
+            android:contentDescription="@string/accessibility_quick_settings_power_menu"
+            android:tint="?androidprv:attr/textColorOnAccent" />
+
+    </LinearLayout>
+</com.android.systemui.qs.FooterActionsView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/action_chip_background.xml b/packages/SystemUI/res/drawable/action_chip_background.xml
index eeff39b..745470f 100644
--- a/packages/SystemUI/res/drawable/action_chip_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_background.xml
@@ -17,11 +17,11 @@
 <ripple
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:color="@color/screenshot_button_ripple">
+    android:color="@color/overlay_button_ripple">
     <item android:id="@android:id/background">
         <shape android:shape="rectangle">
             <solid android:color="?androidprv:attr/colorAccentSecondary"/>
-            <corners android:radius="@dimen/screenshot_button_corner_radius"/>
+            <corners android:radius="@dimen/overlay_button_corner_radius"/>
         </shape>
     </item>
 </ripple>
diff --git a/packages/SystemUI/res/drawable/action_chip_container_background.xml b/packages/SystemUI/res/drawable/action_chip_container_background.xml
index 72767a1..36083f1 100644
--- a/packages/SystemUI/res/drawable/action_chip_container_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_container_background.xml
@@ -19,5 +19,5 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
     <solid android:color="?androidprv:attr/colorSurface"/>
-    <corners android:radius="@dimen/screenshot_action_container_corner_radius"/>
+    <corners android:radius="@dimen/overlay_action_container_corner_radius"/>
 </shape>
diff --git a/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml b/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
index c547c52..ec9465b 100644
--- a/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
+++ b/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
@@ -20,6 +20,6 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM14,6h-4L10,4h4v2z"
+        android:pathData="@*android:string/config_work_badge_path_24"
         android:fillColor="?android:attr/colorAccent"/>
 </vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_next.xml b/packages/SystemUI/res/drawable/ic_media_next.xml
new file mode 100644
index 0000000..016653b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_next.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="12dp"
+    android:height="12dp"
+    android:viewportWidth="12"
+    android:viewportHeight="12">
+    <path
+        android:pathData="M0,12L8.5,6L0,0V12ZM2,3.86L5.03,6L2,8.14V3.86ZM12,0H10V12H12V0Z"
+        android:fillColor="#ffffff"
+        android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_pause.xml b/packages/SystemUI/res/drawable/ic_media_pause.xml
new file mode 100644
index 0000000..1f4b2cf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_pause.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="14dp"
+    android:height="16dp"
+    android:viewportWidth="14"
+    android:viewportHeight="16">
+    <path
+        android:pathData="M9.1818,15.6363H13.5455V0.3635H9.1818V15.6363ZM0.4546,15.6363H4.8182V0.3635H0.4546V15.6363Z"
+        android:fillColor="#FFFFFF"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_play.xml b/packages/SystemUI/res/drawable/ic_media_play.xml
new file mode 100644
index 0000000..0eac1ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_play.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:pathData="M20,12L6,21V3L20,12ZM15.26,12L8.55,7.68V16.32L15.26,12Z"
+        android:fillColor="#FFFFFF"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_prev.xml b/packages/SystemUI/res/drawable/ic_media_prev.xml
new file mode 100644
index 0000000..b4aeed4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_prev.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="12dp"
+    android:height="12dp"
+    android:viewportWidth="12"
+    android:viewportHeight="12">
+    <path
+        android:pathData="M0,0H2V12H0V0ZM3.5,6L12,12V0L3.5,6ZM6.97,6L10,8.14V3.86L6.97,6Z"
+        android:fillColor="#ffffff"
+        android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
similarity index 91%
rename from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
rename to packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
index dd818a0..d8f5632 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
@@ -17,6 +17,6 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
     <gradient
         android:angle="90"
-        android:startColor="@color/screenshot_background_protection_start"
+        android:startColor="@color/overlay_background_protection_start"
         android:endColor="#00000000"/>
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_button_background.xml b/packages/SystemUI/res/drawable/overlay_button_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_button_background.xml
rename to packages/SystemUI/res/drawable/overlay_button_background.xml
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
new file mode 100644
index 0000000..f54c30f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -0,0 +1,36 @@
+<?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"
+    android:insetTop="@dimen/qs_footer_action_inset"
+    android:insetBottom="@dimen/qs_footer_action_inset"
+    android:insetLeft="@dimen/qs_footer_action_inset"
+    android:insetRight="@dimen/qs_footer_action_inset">
+    <ripple
+        android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="oval">
+                <solid android:color="@android:color/white"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="oval">
+                <solid android:color="?attr/offStateColor"/>
+            </shape>
+        </item>
+
+    </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
new file mode 100644
index 0000000..1a323bc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -0,0 +1,36 @@
+<?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"
+    android:insetTop="@dimen/qs_footer_action_inset"
+    android:insetBottom="@dimen/qs_footer_action_inset"
+    android:insetLeft="@dimen/qs_footer_action_inset"
+    android:insetRight="@dimen/qs_footer_action_inset">
+    <ripple
+        android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="oval">
+                <solid android:color="@android:color/white"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="oval">
+                <solid android:color="?android:attr/colorAccent"/>
+            </shape>
+        </item>
+
+    </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/qs_media_scrim.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to packages/SystemUI/res/drawable/qs_media_scrim.xml
index dd818a0..2ec319c 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/drawable/qs_media_scrim.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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.
@@ -12,11 +12,15 @@
   ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT 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
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/notification_corner_radius"/>
+    <!-- gradient from 25% in the center to 100% at edges -->
     <gradient
-        android:angle="90"
-        android:startColor="@color/screenshot_background_protection_start"
-        android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+        android:type="radial"
+        android:gradientRadius="100%p"
+        android:startColor="#40000000"
+        android:endColor="#FF000000" />
+</shape>
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 1e0ce00..0ff1db2 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -18,64 +18,72 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:gravity="center_horizontal"
-    android:elevation="@dimen/biometric_dialog_elevation">
+    android:elevation="@dimen/biometric_dialog_elevation"
+    android:orientation="vertical">
 
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_weight="1"/>
-
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-
-    <TextView
-        android:id="@+id/title"
+    <RelativeLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Title"/>
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-    <TextView
-        android:id="@+id/subtitle"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Subtitle"/>
+        <LinearLayout
+            android:id="@+id/auth_credential_header"
+            style="@style/AuthCredentialHeaderStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true">
 
-    <TextView
-        android:id="@+id/description"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Description"/>
+            <ImageView
+                android:id="@+id/icon"
+                android:layout_width="48dp"
+                android:layout_height="48dp"
+                android:contentDescription="@null" />
 
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_weight="1"/>
+            <TextView
+                android:id="@+id/title"
+                style="@style/TextAppearance.AuthNonBioCredential.Title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
 
-    <ImeAwareEditText
-        android:id="@+id/lockPassword"
-        android:layout_width="208dp"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:minHeight="48dp"
-        android:gravity="center"
-        android:inputType="textPassword"
-        android:maxLength="500"
-        android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
-        style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
+            <TextView
+                android:id="@+id/subtitle"
+                style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
 
-    <TextView
-        android:id="@+id/error"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Error"/>
+            <TextView
+                android:id="@+id/description"
+                style="@style/TextAppearance.AuthNonBioCredential.Description"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
 
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_weight="5"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:layout_alignParentBottom="true">
+
+            <ImeAwareEditText
+                android:id="@+id/lockPassword"
+                style="@style/TextAppearance.AuthCredential.PasswordEntry"
+                android:layout_width="208dp"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+                android:inputType="textPassword"
+                android:minHeight="48dp" />
+
+            <TextView
+                android:id="@+id/error"
+                style="@style/TextAppearance.AuthNonBioCredential.Error"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+    </RelativeLayout>
 
 </com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4939ea2..dada981 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -22,76 +22,81 @@
     android:gravity="center_horizontal"
     android:elevation="@dimen/biometric_dialog_elevation">
 
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_weight="1"/>
-
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-
-    <TextView
-        android:id="@+id/title"
+    <RelativeLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Title"/>
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-    <TextView
-        android:id="@+id/subtitle"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Subtitle"/>
+        <LinearLayout
+            android:id="@+id/auth_credential_header"
+            style="@style/AuthCredentialHeaderStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
 
-    <TextView
-        android:id="@+id/description"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.AuthCredential.Description"/>
+            <ImageView
+                android:id="@+id/icon"
+                android:layout_width="48dp"
+                android:layout_height="48dp"
+                android:contentDescription="@null" />
 
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_weight="1"/>
+            <TextView
+                android:id="@+id/title"
+                style="@style/TextAppearance.AuthNonBioCredential.Title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:gravity="center"
-        android:paddingLeft="0dp"
-        android:paddingRight="0dp"
-        android:paddingTop="0dp"
-        android:paddingBottom="16dp"
-        android:clipToPadding="false">
+            <TextView
+                android:id="@+id/subtitle"
+                style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
 
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            style="@style/LockPatternContainerStyle">
+            <TextView
+                android:id="@+id/description"
+                style="@style/TextAppearance.AuthNonBioCredential.Description"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
 
-            <com.android.internal.widget.LockPatternView
-                android:id="@+id/lockPattern"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                style="@style/LockPatternStyleBiometricPrompt"/>
-
-        </FrameLayout>
-
-        <TextView
-            android:id="@+id/error"
+        <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            style="@style/TextAppearance.AuthCredential.Error"/>
+            android:layout_below="@id/auth_credential_header"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:paddingBottom="16dp"
+            android:paddingTop="60dp">
 
-    </LinearLayout>
+            <FrameLayout
+                style="@style/LockPatternContainerStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="0dp"
+                android:layout_weight="1">
 
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_weight="1"/>
+                <com.android.internal.widget.LockPatternView
+                    android:id="@+id/lockPattern"
+                    style="@style/LockPatternStyle"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center" />
+
+            </FrameLayout>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true">
+
+            <TextView
+                android:id="@+id/error"
+                style="@style/TextAppearance.AuthNonBioCredential.Error"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+    </RelativeLayout>
 
 </com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 2a3761e..4817d45 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -17,6 +17,7 @@
 <com.android.systemui.clipboardoverlay.DraggableConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:theme="@style/FloatingOverlay"
     android:alpha="0"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
@@ -27,7 +28,7 @@
         android:layout_width="0dp"
         android:elevation="1dp"
         android:background="@drawable/action_chip_container_background"
-        android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
         app:layout_constraintBottom_toBottomOf="@+id/actions_container"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="@+id/actions_container"
@@ -36,9 +37,9 @@
         android:id="@+id/actions_container"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
-        android:paddingEnd="@dimen/screenshot_action_container_padding_right"
-        android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
+        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+        android:paddingEnd="@dimen/overlay_action_container_padding_right"
+        android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
         android:elevation="1dp"
         android:scrollbars="none"
         app:layout_constraintHorizontal_bias="0"
@@ -50,10 +51,11 @@
         <LinearLayout
             android:id="@+id/actions"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content">
-            <include layout="@layout/screenshot_action_chip"
+            android:layout_height="wrap_content"
+            android:animateLayoutChanges="true">
+            <include layout="@layout/overlay_action_chip"
                      android:id="@+id/remote_copy_chip"/>
-            <include layout="@layout/screenshot_action_chip"
+            <include layout="@layout/overlay_action_chip"
                      android:id="@+id/edit_chip"/>
         </LinearLayout>
     </HorizontalScrollView>
@@ -64,7 +66,7 @@
         android:layout_marginStart="@dimen/overlay_offset_x"
         android:layout_marginBottom="@dimen/overlay_offset_y"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toBottomOf="@id/actions_container_background"
         android:elevation="@dimen/overlay_preview_elevation"
         app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
         app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index 7fd029c..11a5665 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -35,7 +35,8 @@
         android:layout_height="wrap_content"
         android:clipChildren="false"
         android:orientation="vertical"
-        android:clipToPadding="false" />
+        android:clipToPadding="false"
+        android:paddingHorizontal="@dimen/controls_padding_horizontal" />
 
   </com.android.systemui.globalactions.MinHeightScrollView>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
new file mode 100644
index 0000000..b6f516f
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<TextClock
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/date_view"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="@dimen/dream_overlay_complication_clock_date_padding_left"
+    android:paddingBottom="@dimen/dream_overlay_complication_clock_date_padding_bottom"
+    android:gravity="center_horizontal"
+    android:textColor="@android:color/white"
+    android:shadowColor="@color/keyguard_shadow_color"
+    android:shadowRadius="?attr/shadowRadius"
+    android:format12Hour="EEE, MMM d"
+    android:format24Hour="EEE, MMM d"
+    android:textSize="@dimen/dream_overlay_complication_clock_date_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
new file mode 100644
index 0000000..a41d34f
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<TextClock
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/time_view"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="@dimen/dream_overlay_complication_clock_time_padding_left"
+    android:fontFamily="sans-serif-thin"
+    android:textColor="@android:color/white"
+    android:format12Hour="h:mm"
+    android:format24Hour="kk:mm"
+    android:shadowColor="@color/keyguard_shadow_color"
+    android:shadowRadius="?attr/shadowRadius"
+    android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
new file mode 100644
index 0000000..08f0d67
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_weather.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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/weather_view"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="@dimen/dream_overlay_complication_weather_padding_left"
+    android:paddingBottom="@dimen/dream_overlay_complication_weather_padding_bottom"
+    android:textColor="@android:color/white"
+    android:shadowColor="@color/keyguard_shadow_color"
+    android:shadowRadius="?attr/shadowRadius"
+    android:textSize="@dimen/dream_overlay_complication_weather_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
index 5135947..f4eb32f 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
@@ -20,30 +20,28 @@
     android:id="@+id/dream_overlay_complications_layer"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <TextClock
-        android:id="@+id/time_view"
+    <androidx.constraintlayout.widget.Guideline
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:fontFamily="sans-serif-thin"
-        android:format12Hour="h:mm"
-        android:format24Hour="kk:mm"
-        android:shadowColor="#B2000000"
-        android:shadowRadius="2.0"
-        android:singleLine="true"
-        android:textSize="72sp"
-        app:layout_constraintBottom_toTopOf="@+id/date_view"
-        app:layout_constraintStart_toStartOf="parent" />
-    <TextClock
-        android:id="@+id/date_view"
+        android:id="@+id/complication_top_guide"
+        app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_top_percent"
+        android:orientation="horizontal"/>
+    <androidx.constraintlayout.widget.Guideline
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:shadowColor="#B2000000"
-        android:shadowRadius="2.0"
-        android:format12Hour="EEE, MMM d"
-        android:format24Hour="EEE, MMM d"
-        android:singleLine="true"
-        android:textSize="18sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="@+id/time_view"
-        app:layout_constraintStart_toStartOf="@+id/time_view" />
+        android:id="@+id/complication_end_guide"
+        app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_end_percent"
+        android:orientation="vertical"/>
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/complication_bottom_guide"
+        app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_bottom_percent"
+        android:orientation="horizontal"/>
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/complication_start_guide"
+        app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_start_percent"
+        android:orientation="vertical"/>
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 4929f50..3c2183d 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -25,7 +25,7 @@
         android:id="@+id/dream_overlay_content"
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/dream_overlay_status_bar"
         app:layout_constraintBottom_toBottomOf="parent" />
 
     <com.android.systemui.dreams.DreamOverlayStatusBarView
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 856697c..cdf6103 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -18,7 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:background="?android:colorBackgroundFloating"
     android:id="@+id/root"
     android:layout_width="match_parent"
@@ -32,7 +31,7 @@
         android:text="@string/save"
         android:layout_marginStart="8dp"
         android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
-        android:background="@drawable/screenshot_button_background"
+        android:background="@drawable/overlay_button_background"
         android:textColor="?android:textColorSecondary"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
@@ -46,7 +45,7 @@
         android:text="@android:string/cancel"
         android:layout_marginStart="6dp"
         android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
-        android:background="@drawable/screenshot_button_background"
+        android:background="@drawable/overlay_button_background"
         android:textColor="?android:textColorSecondary"
         app:layout_constraintStart_toEndOf="@id/save"
         app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index cc02fea..51d1608 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -28,6 +28,22 @@
     android:background="@drawable/qs_media_background"
     android:theme="@style/MediaPlayer">
 
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="184dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:translationZ="0dp"
+        android:id="@+id/album_art"
+        android:scaleType="centerCrop"
+        android:adjustViewBounds="true"
+        android:clipToOutline="true"
+        android:foreground="@drawable/qs_media_scrim"
+        android:background="@drawable/qs_media_scrim"
+        />
+
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/center_vertical_guideline"
         android:layout_width="wrap_content"
@@ -281,6 +297,7 @@
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintTop_toBottomOf="@id/remove_text">
         <TextView
+            android:id="@+id/cancel_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center|bottom"
@@ -304,6 +321,7 @@
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintTop_toBottomOf="@id/remove_text">
         <TextView
+            android:id="@+id/dismiss_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index b546a9c..9471b9f 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -264,6 +264,7 @@
         app:layout_constraintTop_toBottomOf="@id/remove_text">
 
         <TextView
+            android:id="@+id/cancel_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center|bottom"
@@ -288,6 +289,7 @@
         app:layout_constraintTop_toBottomOf="@id/remove_text">
 
         <TextView
+            android:id="@+id/dismiss_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/screenshot_action_chip.xml b/packages/SystemUI/res/layout/overlay_action_chip.xml
similarity index 61%
rename from packages/SystemUI/res/layout/screenshot_action_chip.xml
rename to packages/SystemUI/res/layout/overlay_action_chip.xml
index b80469f..e0c20ff 100644
--- a/packages/SystemUI/res/layout/screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/overlay_action_chip.xml
@@ -14,33 +14,34 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.screenshot.ScreenshotActionChip
+<com.android.systemui.screenshot.OverlayActionChip
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/screenshot_action_chip"
+    android:id="@+id/overlay_action_chip"
+    android:theme="@style/FloatingOverlay"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_marginStart="@dimen/screenshot_action_chip_margin_start"
-    android:paddingVertical="@dimen/screenshot_action_chip_margin_vertical"
+    android:layout_marginStart="@dimen/overlay_action_chip_margin_start"
+    android:paddingVertical="@dimen/overlay_action_chip_margin_vertical"
     android:layout_gravity="center"
     android:gravity="center"
     android:alpha="0.0">
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
+        android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
         android:background="@drawable/action_chip_background"
         android:gravity="center">
         <ImageView
-            android:id="@+id/screenshot_action_chip_icon"
-            android:tint="?android:attr/textColorPrimary"
-            android:layout_width="@dimen/screenshot_action_chip_icon_size"
-            android:layout_height="@dimen/screenshot_action_chip_icon_size"/>
+            android:id="@+id/overlay_action_chip_icon"
+            android:tint="?attr/overlayButtonTextColor"
+            android:layout_width="@dimen/overlay_action_chip_icon_size"
+            android:layout_height="@dimen/overlay_action_chip_icon_size"/>
         <TextView
-            android:id="@+id/screenshot_action_chip_text"
+            android:id="@+id/overlay_action_chip_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:textSize="@dimen/screenshot_action_chip_text_size"
-            android:textColor="?android:attr/textColorPrimary"/>
+            android:textSize="@dimen/overlay_action_chip_text_size"
+            android:textColor="?attr/overlayButtonTextColor"/>
     </LinearLayout>
-</com.android.systemui.screenshot.ScreenshotActionChip>
+</com.android.systemui.screenshot.OverlayActionChip>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 5cd9e94..b6e3499 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -19,7 +19,7 @@
 <com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/qs_footer"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/qs_footer_height"
+    android:layout_height="wrap_content"
     android:layout_marginStart="@dimen/qs_footer_margin"
     android:layout_marginEnd="@dimen/qs_footer_margin"
     android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
@@ -36,7 +36,7 @@
 
         <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="48dp"
+            android:layout_height="@dimen/qs_footer_height"
             android:layout_gravity="center_vertical">
 
             <TextView
@@ -80,8 +80,13 @@
 
         </LinearLayout>
 
-        <include layout="@layout/footer_actions"
-            android:id="@+id/qs_footer_actions"/>
+        <ViewStub
+            android:id="@+id/footer_stub"
+            android:inflatedId="@+id/qs_footer_actions"
+            android:layout="@layout/footer_actions"
+            android:layout_height="@dimen/qs_footer_height"
+            android:layout_width="match_parent"
+        />
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index f5c6036..22abd0c 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -51,6 +51,15 @@
         android:id="@+id/qs_detail"
         layout="@layout/qs_detail" />
 
+    <ViewStub
+        android:id="@+id/container_stub"
+        android:inflatedId="@+id/qs_footer_actions"
+        android:layout="@layout/new_footer_actions"
+        android:layout_height="@dimen/qs_footer_height"
+        android:layout_width="match_parent"
+        android:layout_gravity="bottom"
+        />
+
     <include
         android:id="@+id/qs_customize"
         layout="@layout/qs_customize_panel"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 10a2f4c..2c29f07 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -57,16 +57,6 @@
             android:focusable="true"
             android:paddingBottom="24dp"
             android:importantForAccessibility="yes">
-
-            <include
-                layout="@layout/footer_actions"
-                android:id="@+id/qqs_footer_actions"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/qqs_layout_margin_top"
-                android:layout_marginStart="@dimen/qs_footer_margin"
-                android:layout_marginEnd="@dimen/qs_footer_margin"
-                />
         </com.android.systemui.qs.QuickQSPanel>
     </RelativeLayout>
 
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index 227212b..890dbe5 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -17,7 +17,7 @@
 <com.android.systemui.screenshot.ScreenshotView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/screenshot_frame"
-    android:theme="@style/Screenshot"
+    android:theme="@style/FloatingOverlay"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:importantForAccessibility="no">
@@ -30,11 +30,11 @@
         android:importantForAccessibility="no"/>
     <ImageView
         android:id="@+id/screenshot_actions_background"
-        android:layout_height="@dimen/screenshot_bg_protection_height"
+        android:layout_height="@dimen/overlay_bg_protection_height"
         android:layout_width="match_parent"
         android:layout_gravity="bottom"
         android:alpha="0.0"
-        android:src="@drawable/screenshot_actions_background_protection"/>
+        android:src="@drawable/overlay_actions_background_protection"/>
     <ImageView
         android:id="@+id/screenshot_flash"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 8f791c3..813bb60 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -26,7 +26,7 @@
         android:layout_width="0dp"
         android:elevation="1dp"
         android:background="@drawable/action_chip_container_background"
-        android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
         app:layout_constraintBottom_toBottomOf="@+id/screenshot_actions_container"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="@+id/screenshot_actions_container"
@@ -35,9 +35,9 @@
         android:id="@+id/screenshot_actions_container"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
-        android:paddingEnd="@dimen/screenshot_action_container_padding_right"
-        android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
+        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+        android:paddingEnd="@dimen/overlay_action_container_padding_right"
+        android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
         android:elevation="1dp"
         android:scrollbars="none"
         app:layout_constraintHorizontal_bias="0"
@@ -50,11 +50,11 @@
             android:id="@+id/screenshot_actions"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content">
-            <include layout="@layout/screenshot_action_chip"
+            <include layout="@layout/overlay_action_chip"
                      android:id="@+id/screenshot_share_chip"/>
-            <include layout="@layout/screenshot_action_chip"
+            <include layout="@layout/overlay_action_chip"
                      android:id="@+id/screenshot_edit_chip"/>
-            <include layout="@layout/screenshot_action_chip"
+            <include layout="@layout/overlay_action_chip"
                      android:id="@+id/screenshot_scroll_chip"
                      android:visibility="gone" />
         </LinearLayout>
@@ -89,7 +89,7 @@
     <ImageView
         android:id="@+id/screenshot_preview"
         android:visibility="invisible"
-        android:layout_width="@dimen/screenshot_x_scale"
+        android:layout_width="@dimen/overlay_x_scale"
         android:layout_margin="@dimen/overlay_border_width"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 2290964..39d7f4f 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -122,7 +122,7 @@
             android:layout_marginTop="@dimen/notification_panel_margin_top"
             android:layout_width="@dimen/notification_panel_width"
             android:layout_height="match_parent"
-            android:layout_marginBottom="@dimen/close_handle_underlap"
+            android:layout_marginBottom="@dimen/notification_panel_margin_bottom"
             android:importantForAccessibility="no"
             systemui:layout_constraintStart_toStartOf="parent"
             systemui:layout_constraintEnd_toEndOf="parent"
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index ac4dfd2..8c5006d 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -31,9 +31,6 @@
     <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
     <integer name="navigation_bar_deadzone_orientation">1</integer>
 
-    <!-- Max number of columns for quick controls area -->
-    <integer name="controls_max_columns">4</integer>
-
     <!-- Max number of columns for power menu -->
     <integer name="power_menu_max_columns">4</integer>
 </resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index fc5edf3..9d24e9b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -66,4 +66,6 @@
     <dimen name="controls_management_favorites_top_margin">8dp</dimen>
 
     <dimen name="wallet_card_carousel_container_top_margin">24dp</dimen>
+
+    <dimen name="large_dialog_width">348dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 3412722..b318bbc 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -58,9 +58,9 @@
     <!-- The color of the text in the Global Actions menu -->
     <color name="global_actions_alert_text">@color/GM2_red_300</color>
 
-    <!-- Global screenshot actions -->
-    <color name="screenshot_button_ripple">#42FFFFFF</color>
-    <color name="screenshot_background_protection_start">#80000000</color> <!-- 50% black -->
+    <!-- Floating overlay actions -->
+    <color name="overlay_button_ripple">#42FFFFFF</color>
+    <color name="overlay_background_protection_start">#80000000</color> <!-- 50% black -->
 
     <!-- Media -->
     <color name="media_divider">#85ffffff</color>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 1f815b7..f7261e7 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -47,8 +47,8 @@
         <item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
     </style>
 
-    <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight">
-        <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
+    <style name="FloatingOverlay" parent="@android:style/Theme.DeviceDefault.DayNight">
+        <item name="overlayButtonTextColor">?android:attr/textColorPrimaryInverse</item>
     </style>
 
     <style name="Theme.PeopleTileConfigActivity" parent="@style/Theme.SystemUI">
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index dabc310..fe546f6 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -15,9 +15,6 @@
   ~ limitations under the License
   -->
 <resources>
-    <!-- Max number of columns for quick controls area -->
-    <integer name="controls_max_columns">2</integer>
-
     <!-- The maximum number of rows in the QSPanel -->
     <integer name="quick_settings_max_rows">3</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 89d046b..c2cec52 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -26,6 +26,10 @@
          keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
     <dimen name="keyguard_clock_top_margin">8dp</dimen>
 
+    <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen>
+
+    <dimen name="notification_panel_margin_bottom">48dp</dimen>
+
     <!-- Limit the TaskView to this percentage of the overall screen width (0.0 - 1.0) -->
     <item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item>
     <dimen name="controls_task_view_right_margin">8dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index 02fd25b..3c6a81e 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License
   -->
 <resources>
-
     <!-- The maximum number of tiles in the QuickQSPanel -->
     <integer name="quick_qs_panel_max_tiles">6</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index f5dc7e3e..1b8453a 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -29,9 +29,6 @@
     <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
     <integer name="navigation_bar_deadzone_orientation">0</integer>
 
-    <!-- Max number of columns for quick controls area -->
-    <integer name="controls_max_columns">4</integer>
-
     <!-- How many lines to show in the security footer -->
     <integer name="qs_security_footer_maxLines">1</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7d03301..a66ed15 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -69,5 +69,5 @@
     <dimen name="qs_detail_margin_top">0dp</dimen>
 
     <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
-    <dimen name="large_dialog_width">504dp</dimen>
+    <dimen name="large_dialog_width">472dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index ae89ef4..be34a48 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -15,9 +15,6 @@
   ~ limitations under the License
   -->
 <resources>
-    <!-- Max number of columns for quick controls area -->
-    <integer name="controls_max_columns">2</integer>
-
     <!-- The maximum number of rows in the QSPanel -->
     <integer name="quick_settings_max_rows">3</integer>
 
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
similarity index 62%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to packages/SystemUI/res/values-sw720dp-land/dimens.xml
index cb602d79..ae557c4 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -1,5 +1,7 @@
-/**
- * Copyright (c) 2021, The Android Open Source Project
+<?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.
@@ -12,8 +14,10 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- */
-
-package android.net;
-
-parcelable NetworkStateSnapshot;
+*/
+-->
+<resources>
+    <dimen name="controls_padding_horizontal">205dp</dimen>
+    <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen>
+    <dimen name="notification_panel_margin_bottom">56dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 1564ee8..95df594 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,8 +16,9 @@
 */
 -->
 <resources>
-
     <!-- gap on either side of status bar notification icons -->
     <dimen name="status_bar_icon_padding">1dp</dimen>
+
+    <dimen name="controls_padding_horizontal">75dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/values-w500dp/config.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to packages/SystemUI/res/values-w500dp/config.xml
index dd818a0..ef499ff 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/values-w500dp/config.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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,9 +13,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <gradient
-        android:angle="90"
-        android:startColor="@color/screenshot_background_protection_start"
-        android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+
+<resources>
+    <!-- Max number of columns for quick controls area -->
+    <integer name="controls_max_columns">3</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/values-w850dp/config.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to packages/SystemUI/res/values-w850dp/config.xml
index dd818a0..337ebe1 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/values-w850dp/config.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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,9 +13,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <gradient
-        android:angle="90"
-        android:startColor="@color/screenshot_background_protection_start"
-        android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+
+<resources>
+    <!-- Max number of columns for quick controls area -->
+    <integer name="controls_max_columns">4</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index db69924..e6ab0ff 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -85,7 +85,7 @@
          Contract: Pixel with fillColor blended over backgroundColor blended over translucent should
          equal to singleToneColor blended over translucent. -->
     <declare-styleable name="TonedIcon">
-        <attr name="backgroundColor" format="integer" />
+        <attr name="iconBackgroundColor" format="integer" />
         <attr name="fillColor" format="integer" />
         <attr name="singleToneColor" format="integer" />
         <attr name="homeHandleColor" format="integer" />
@@ -204,5 +204,7 @@
         <attr name="singleLineVerticalPadding" format="dimension" />
         <attr name="textViewId" format="reference" />
     </declare-styleable>
+
+    <attr name="overlayButtonTextColor" format="color" />
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 81e3e04..3ab569a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -138,9 +138,9 @@
     <color name="udfps_enroll_progress">#7DA7F1</color>
     <color name="udfps_enroll_progress_help">#ffEE675C</color>
 
-    <!-- Global screenshot actions -->
-    <color name="screenshot_button_ripple">#1f000000</color>
-    <color name="screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
+    <!-- Floating overlay actions -->
+    <color name="overlay_button_ripple">#1f000000</color>
+    <color name="overlay_background_protection_start">#40000000</color> <!-- 25% black -->
 
     <!-- GM2 colors -->
     <color name="GM2_grey_100">#F1F3F4</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b35571c..dba7290 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -258,36 +258,36 @@
 
     <!-- Dimensions related to screenshots -->
 
-    <!-- The padding on the global screenshot background image -->
-    <dimen name="screenshot_x_scale">80dp</dimen>
-    <dimen name="screenshot_bg_protection_height">242dp</dimen>
-    <dimen name="screenshot_action_container_corner_radius">18dp</dimen>
-    <dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
-    <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
-    <dimen name="screenshot_action_container_padding_right">8dp</dimen>
-    <!-- Radius of the chip background on global screenshot actions -->
-    <dimen name="screenshot_button_corner_radius">8dp</dimen>
-    <!-- Margin between successive chips -->
-    <dimen name="screenshot_action_chip_margin_start">8dp</dimen>
-    <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) -->
-    <dimen name="screenshot_action_chip_margin_vertical">4dp</dimen>
-    <dimen name="screenshot_action_chip_padding_vertical">11dp</dimen>
-    <dimen name="screenshot_action_chip_icon_size">18sp</dimen>
-    <!-- Padding on each side of the icon for icon-only chips -->
-    <dimen name="screenshot_action_chip_icon_only_padding_horizontal">14dp</dimen>
-    <!-- Padding at the edges of the chip for icon-and-text chips -->
-    <dimen name="screenshot_action_chip_padding_horizontal">12dp</dimen>
-    <!-- Spacing between chip icon and chip text -->
-    <dimen name="screenshot_action_chip_spacing">8dp</dimen>
-    <dimen name="screenshot_action_chip_text_size">14sp</dimen>
-    <dimen name="screenshot_dismissal_height_delta">80dp</dimen>
+
     <dimen name="screenshot_crop_handle_thickness">3dp</dimen>
     <dimen name="long_screenshot_action_bar_top_margin">8dp</dimen>
 
     <!-- Dimensions shared between "overlays" (clipboard and screenshot preview UIs) -->
+    <!-- Constrained size of the floating overlay preview -->
+    <dimen name="overlay_x_scale">80dp</dimen>
+    <!-- Radius of the chip background on floating overlay actions -->
+    <dimen name="overlay_button_corner_radius">8dp</dimen>
+    <!-- Margin between successive chips -->
+    <dimen name="overlay_action_chip_margin_start">8dp</dimen>
+    <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) -->
+    <dimen name="overlay_action_chip_margin_vertical">4dp</dimen>
+    <dimen name="overlay_action_chip_padding_vertical">11dp</dimen>
+    <dimen name="overlay_action_chip_icon_size">18sp</dimen>
+    <!-- Padding on each side of the icon for icon-only chips -->
+    <dimen name="overlay_action_chip_icon_only_padding_horizontal">14dp</dimen>
+    <!-- Padding at the edges of the chip for icon-and-text chips -->
+    <dimen name="overlay_action_chip_padding_horizontal">12dp</dimen>
+    <!-- Spacing between chip icon and chip text -->
+    <dimen name="overlay_action_chip_spacing">8dp</dimen>
+    <dimen name="overlay_action_chip_text_size">14sp</dimen>
     <dimen name="overlay_offset_y">8dp</dimen>
     <dimen name="overlay_offset_x">16dp</dimen>
     <dimen name="overlay_preview_elevation">4dp</dimen>
+    <dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
+    <dimen name="overlay_bg_protection_height">242dp</dimen>
+    <dimen name="overlay_action_container_corner_radius">18dp</dimen>
+    <dimen name="overlay_action_container_padding_vertical">4dp</dimen>
+    <dimen name="overlay_action_container_padding_right">8dp</dimen>
     <dimen name="overlay_dismiss_button_elevation">7dp</dimen>
     <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
     <dimen name="overlay_dismiss_button_margin">8dp</dimen>
@@ -295,7 +295,7 @@
     <!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 -->
     <dimen name="overlay_border_width_neg">-4dp</dimen>
 
-    <dimen name="clipboard_preview_size">@dimen/screenshot_x_scale</dimen>
+    <dimen name="clipboard_preview_size">@dimen/overlay_x_scale</dimen>
 
 
     <!-- The width of the view containing navigation buttons -->
@@ -311,9 +311,6 @@
     <!-- Move the back button drawable for 3 button layout upwards in ime mode and in portrait -->
     <dimen name="navbar_back_button_ime_offset">2dp</dimen>
 
-    <!-- Amount of close_handle that will NOT overlap the notification list -->
-    <dimen name="close_handle_underlap">32dp</dimen>
-
     <!-- Height of the status bar header bar in the car setting. -->
     <dimen name="car_status_bar_header_height">128dp</dimen>
 
@@ -330,7 +327,7 @@
 
     <!-- The height of the quick settings footer that holds the user switcher, settings icon,
          etc. -->
-    <dimen name="qs_footer_height">96dp</dimen>
+    <dimen name="qs_footer_height">48dp</dimen>
 
     <!-- The size of each of the icon buttons in the QS footer -->
     <dimen name="qs_footer_action_button_size">48dp</dimen>
@@ -376,8 +373,12 @@
     -->
     <dimen name="nssl_split_shade_min_content_height">256dp</dimen>
 
-    <!-- The bottom margin of the panel that holds the list of notifications. -->
-    <dimen name="notification_panel_margin_bottom">0dp</dimen>
+    <dimen name="notification_panel_margin_bottom">32dp</dimen>
+
+    <!-- The bottom padding of the panel that holds the list of notifications. -->
+    <dimen name="notification_panel_padding_bottom">0dp</dimen>
+
+    <dimen name="split_shade_notifications_scrim_margin_bottom">0dp</dimen>
 
     <dimen name="notification_panel_width">@dimen/match_parent</dimen>
 
@@ -490,7 +491,7 @@
     <dimen name="qs_tile_text_size">14sp</dimen>
     <dimen name="qs_panel_padding">16dp</dimen>
     <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
-    <dimen name="qs_panel_padding_bottom">0dp</dimen>
+    <dimen name="qs_panel_padding_bottom">@dimen/qs_footer_height</dimen>
     <dimen name="qs_panel_padding_top">48dp</dimen>
     <dimen name="qs_detail_header_padding">0dp</dimen>
     <dimen name="qs_detail_image_width">56dp</dimen>
@@ -1035,6 +1036,7 @@
     <dimen name="controls_header_bottom_margin">24dp</dimen>
     <dimen name="controls_header_app_icon_size">24dp</dimen>
     <dimen name="controls_top_margin">48dp</dimen>
+    <dimen name="controls_padding_horizontal">0dp</dimen>
     <dimen name="control_header_text_size">20sp</dimen>
     <dimen name="control_item_text_size">16sp</dimen>
     <dimen name="control_menu_item_text_size">16sp</dimen>
@@ -1343,4 +1345,47 @@
     <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
          shade. -->
     <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
+
+    <!-- Dream overlay complications related dimensions -->
+    <dimen name="dream_overlay_complication_clock_time_padding_left">50dp</dimen>
+    <dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen>
+    <dimen name="dream_overlay_complication_clock_date_padding_left">60dp</dimen>
+    <dimen name="dream_overlay_complication_clock_date_padding_bottom">50dp</dimen>
+    <dimen name="dream_overlay_complication_clock_date_text_size">18sp</dimen>
+    <dimen name="dream_overlay_complication_weather_padding_left">20dp</dimen>
+    <dimen name="dream_overlay_complication_weather_padding_bottom">50dp</dimen>
+    <dimen name="dream_overlay_complication_weather_text_size">18sp</dimen>
+
+    <!-- The position of the end guide, which dream overlay complications can align their start with
+         if their end is aligned with the parent end. Represented as the percentage over from the
+         start of the parent container. -->
+    <item name="dream_overlay_complication_guide_end_percent" format="float" type="dimen">
+        0.75
+    </item>
+
+    <!-- The position of the start guide, which dream overlay complications can align their end to
+         if their start is aligned with the parent start. Represented as the percentage over from
+         the start of the parent container. -->
+    <item name="dream_overlay_complication_guide_start_percent" format="float" type="dimen">
+        0.25
+    </item>
+
+    <!-- The position of the bottom guide, which dream overlay complications can align their top to
+         if their bottom is aligned with the parent bottom. Represented as the percentage over from
+         the top of the parent container. -->
+    <item name="dream_overlay_complication_guide_bottom_percent" format="float" type="dimen">
+        0.90
+    </item>
+
+    <!-- The position of the top guide, which dream overlay complications can align their bottom to
+     if their top is aligned with the parent top. Represented as the percentage over from
+     the top of the parent container. -->
+    <item name="dream_overlay_complication_guide_top_percent" format="float" type="dimen">
+        0.10
+    </item>
+
+    <!-- The percentage of the screen from which a swipe can start to reveal the bouncer. -->
+    <item name="dream_overlay_bouncer_start_region_screen_percentage" format="float" type="dimen">
+        .2
+    </item>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 75ae52c..4dca0b0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -123,6 +123,18 @@
     <!-- 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>
 
+    <!-- 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>
+
+    <!-- Description for the <Set Menu Language> confirmation dialog [CHAR LIMIT=NONE] -->
+    <string name="hdmi_cec_set_menu_language_description">System language change requested by another device</string>
+
+    <!-- Button label for accepting language change [CHAR LIMIT=25] -->
+    <string name="hdmi_cec_set_menu_language_accept">Change language</string>
+
+    <!-- Button label for declining language change [CHAR LIMIT=25] -->
+    <string name="hdmi_cec_set_menu_language_decline">Keep current language</string>
+
     <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] -->
     <string name="wifi_debugging_title">Allow wireless debugging on this network?</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ac98739..590cc9b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -236,6 +236,41 @@
         <item name="android:textColor">?android:attr/colorError</item>
     </style>
 
+    <style name="TextAppearance.AuthNonBioCredential"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:accessibilityLiveRegion">polite</item>
+        <item name="android:textAlignment">gravity</item>
+        <item name="android:layout_gravity">top</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Title">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">20dp</item>
+        <item name="android:textSize">36sp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">20dp</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Description">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:layout_marginTop">20dp</item>
+        <item name="android:textSize">16sp</item>
+    </style>
+
+    <style name="TextAppearance.AuthNonBioCredential.Error">
+        <item name="android:paddingTop">6dp</item>
+        <item name="android:paddingBottom">18dp</item>
+        <item name="android:paddingHorizontal">24dp</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:attr/colorError</item>
+        <item name="android:gravity">center</item>
+    </style>
+
     <style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:gravity">center</item>
         <item name="android:singleLine">true</item>
@@ -243,6 +278,15 @@
         <item name="android:textSize">24sp</item>
     </style>
 
+    <style name="AuthCredentialHeaderStyle">
+        <item name="android:paddingStart">48dp</item>
+        <item name="android:paddingEnd">24dp</item>
+        <item name="android:paddingTop">28dp</item>
+        <item name="android:paddingBottom">20dp</item>
+        <item name="android:orientation">vertical</item>
+        <item name="android:layout_gravity">top</item>
+    </style>
+
     <style name="DeviceManagementDialogTitle">
         <item name="android:gravity">center</item>
         <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
@@ -307,9 +351,8 @@
         <item name="android:maxWidth">420dp</item>
         <item name="android:minHeight">0dp</item>
         <item name="android:minWidth">0dp</item>
-        <item name="android:paddingBottom">0dp</item>
-        <item name="android:paddingHorizontal">44dp</item>
-        <item name="android:paddingTop">0dp</item>
+        <item name="android:paddingHorizontal">60dp</item>
+        <item name="android:paddingBottom">40dp</item>
     </style>
 
     <style name="LockPatternStyle">
@@ -404,19 +447,19 @@
     </style>
 
     <style name="DualToneLightTheme">
-        <item name="backgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
+        <item name="iconBackgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
         <item name="fillColor">@color/light_mode_icon_color_dual_tone_fill</item>
         <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
         <item name="homeHandleColor">@color/navigation_bar_home_handle_light_color</item>
     </style>
     <style name="DualToneDarkTheme">
-        <item name="backgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
+        <item name="iconBackgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
         <item name="fillColor">@color/dark_mode_icon_color_dual_tone_fill</item>
         <item name="singleToneColor">@color/dark_mode_icon_color_single_tone</item>
         <item name="homeHandleColor">@color/navigation_bar_home_handle_dark_color</item>
     </style>
     <style name="QSHeaderDarkTheme">
-        <item name="backgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
+        <item name="iconBackgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
         <item name="fillColor">@color/dark_mode_qs_icon_color_dual_tone_fill</item>
         <item name="singleToneColor">@color/dark_mode_qs_icon_color_single_tone</item>
     </style>
@@ -664,7 +707,9 @@
         <item name="android:windowActivityTransitions">true</item>
     </style>
 
-    <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight"/>
+    <style name="FloatingOverlay" parent="@android:style/Theme.DeviceDefault.DayNight">
+        <item name="overlayButtonTextColor">?android:attr/textColorPrimary</item>
+    </style>
 
     <!-- Clipboard overlay's edit text activity. -->
     <style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
new file mode 100644
index 0000000..ffab3cd
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.animation
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.lang.ref.WeakReference
+
+/**
+ * Translates items away/towards the hinge when the device is opened/closed, according to the
+ * direction specified in [ViewIdToTranslate.direction], for a maximum of [translationMax] when
+ * progresses are 0.
+ */
+class UnfoldConstantTranslateAnimator(
+    private val viewsIdToTranslate: Set<ViewIdToTranslate>,
+    private val progressProvider: UnfoldTransitionProgressProvider
+) : TransitionProgressListener {
+
+    private var viewsToTranslate = listOf<ViewToTranslate>()
+    private lateinit var rootView: ViewGroup
+    private var translationMax = 0f
+
+    fun init(rootView: ViewGroup, translationMax: Float) {
+        this.rootView = rootView
+        this.translationMax = translationMax
+        progressProvider.addCallback(this)
+    }
+
+    override fun onTransitionStarted() {
+        registerViewsForAnimation(rootView, viewsIdToTranslate)
+    }
+
+    override fun onTransitionProgress(progress: Float) {
+        translateViews(progress)
+    }
+
+    override fun onTransitionFinished() {
+        translateViews(progress = 1f)
+    }
+
+    private fun translateViews(progress: Float) {
+        // progress == 0 -> -translationMax
+        // progress == 1 -> 0
+        val xTrans = (progress - 1f) * translationMax
+        viewsToTranslate.forEach { (view, direction, shouldBeAnimated) ->
+            if (shouldBeAnimated()) {
+                view.get()?.translationX = xTrans * direction.multiplier
+            }
+        }
+    }
+
+    /** Finds in [parent] all views specified by [ids] and register them for the animation. */
+    private fun registerViewsForAnimation(parent: ViewGroup, ids: Set<ViewIdToTranslate>) {
+        viewsToTranslate =
+            ids.mapNotNull { (id, dir, pred) ->
+                parent.findViewById<View>(id)?.let { view ->
+                    ViewToTranslate(WeakReference(view), dir, pred)
+                }
+            }
+    }
+
+    /** Represents a view to animate. [rootView] should contain a view with [viewId] inside. */
+    data class ViewIdToTranslate(
+        val viewId: Int,
+        val direction: Direction,
+        val shouldBeAnimated: () -> Boolean = { true }
+    )
+
+    private data class ViewToTranslate(
+        val view: WeakReference<View>,
+        val direction: Direction,
+        val shouldBeAnimated: () -> Boolean
+    )
+
+    /** Direction of the animation. */
+    enum class Direction(val multiplier: Float) {
+        LEFT(-1f),
+        RIGHT(1f),
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
index 9010d51..fc6bb50 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
@@ -42,7 +42,9 @@
      * are different than actual bounds (e.g. view container may
      * have larger width than width of the items in the container)
      */
-    private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {}
+    private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {},
+    /** Allows to set the alpha based on the progress. */
+    private val alphaProvider: AlphaProvider? = null
 ) : UnfoldTransitionProgressProvider.TransitionProgressListener {
 
     private val screenSize = Point()
@@ -99,17 +101,27 @@
 
     override fun onTransitionProgress(progress: Float) {
         animatedViews.forEach {
-            it.view.get()?.let { view ->
-                translationApplier.apply(
-                    view = view,
-                    x = it.startTranslationX * (1 - progress),
-                    y = it.startTranslationY * (1 - progress)
-                )
-            }
+            it.applyTransition(progress)
+            it.applyAlpha(progress)
         }
         lastAnimationProgress = progress
     }
 
+    private fun AnimatedView.applyTransition(progress: Float) {
+        view.get()?.let { view ->
+            translationApplier.apply(
+                view = view,
+                x = startTranslationX * (1 - progress),
+                y = startTranslationY * (1 - progress)
+            )
+        }
+    }
+
+    private fun AnimatedView.applyAlpha(progress: Float) {
+        if (alphaProvider == null) return
+        view.get()?.alpha = alphaProvider.getAlpha(progress)
+    }
+
     private fun createAnimatedView(view: View): AnimatedView =
         AnimatedView(view = WeakReference(view)).updateAnimatedView(view)
 
@@ -146,6 +158,13 @@
         }
     }
 
+    /** Allows to set a custom alpha based on the progress. */
+    interface AlphaProvider {
+
+        /** Returns the alpha views should have at a given progress. */
+        fun getAlpha(progress: Float): Float
+    }
+
     /**
      * Interface that allows to use custom logic to get the center of the view
      */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl
deleted file mode 100644
index b76be4f..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl
+++ /dev/null
@@ -1,33 +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.communal;
-
-import com.android.systemui.shared.communal.ICommunalSource;
-
-/**
-* An interface, implemented by SystemUI, for hosting a shared, communal surface on the lock
-* screen. Clients declare themselves sources (as defined by ICommunalSource). ICommunalHost is
-* meant only for the input of said sources. The lifetime scope and interactions that follow after
-* are bound to source.
-*/
-oneway interface ICommunalHost {
- /**
-  * Invoked to specify the CommunalSource that should be consulted for communal surfaces to be
-  * displayed.
-  */
- void setSource(in ICommunalSource source) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl
deleted file mode 100644
index 7ef403b..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl
+++ /dev/null
@@ -1,34 +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.communal;
-
-import com.android.systemui.shared.communal.ICommunalSurfaceCallback;
-
-/**
- * An interface, implemented by clients of CommunalHost, to provide communal surfaces for SystemUI.
- * The associated binder proxy will be retained by SystemUI and called on-demand when a communal
- * surface is needed (either new instantiation or update).
- */
-oneway interface ICommunalSource {
-    /**
-     * Called by the CommunalHost when a new communal surface is needed. The provided arguments
-     * match the arguments necessary to construct a SurfaceControlViewHost for producing a
-     * SurfacePackage to return.
-     */
-    void getCommunalSurface(in IBinder hostToken, in int width, in int height, in int displayId,
-        in ICommunalSurfaceCallback callback) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl
deleted file mode 100644
index 3d5998b..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl
+++ /dev/null
@@ -1,30 +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.communal;
-
-import android.view.SurfaceControlViewHost.SurfacePackage;
-
-/**
-* An interface for receiving the result of a surface request. ICommunalSurfaceCallback is
-* implemented by the CommunalHost (SystemUI) to process the results of a new communal surface.
-*/
-interface ICommunalSurfaceCallback {
- /**
-  * Invoked when the CommunalSurface has generated the SurfacePackage to be displayed.
-  */
- void onSurface(in SurfacePackage surfacePackage) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl
new file mode 100644
index 0000000..6db06f0
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.media;
+
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback;
+import com.android.systemui.shared.media.NearbyDevice;
+
+/**
+ * An interface that provides information about nearby devices that are able to play media.
+ *
+ * External clients will implement this interface and System UI will invoke it if it's passed to
+ * SystemUI via {@link INearbyMediaDevicesService.registerProvider}.
+ */
+interface INearbyMediaDevicesProvider {
+  /**
+   * Returns a list of nearby devices that are able to play media.
+   */
+  List<NearbyDevice> getCurrentNearbyDevices() = 1;
+
+  /**
+   * Registers a callback that will be notified each time the status of a nearby device changes.
+   */
+  oneway void registerNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 2;
+
+  /**
+   * Unregisters a callback. See {@link registerNearbyDevicesCallback}.
+   */
+  oneway void unregisterNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 3;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl
new file mode 100644
index 0000000..4f3e10d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.media;
+
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider;
+
+/**
+ * An interface that can be invoked to notify System UI of nearby media devices.
+ *
+ * External clients wanting to notify System UI about the status of nearby media devices should
+ * implement {@link INearbyMediaDevicesProvider} and then register it with system UI using this
+ * service.
+ *
+ * System UI will implement this interface and external clients will invoke it.
+ */
+interface INearbyMediaDevicesService {
+  /** Registers a new provider. */
+  oneway void registerProvider(INearbyMediaDevicesProvider provider) = 1;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl
new file mode 100644
index 0000000..a835f52
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.media;
+
+/**
+ * A callback used to notify implementors of changes in the status of nearby devices that are able
+ * to play media.
+ *
+ * External clients may allow registration of these callbacks and external clients will be
+ * responsible for notifying the callbacks appropriately. System UI is only a mediator between the
+ * external client and these callbacks.
+ */
+interface INearbyMediaDevicesUpdateCallback {
+    /** Unknown distance range. */
+    const int RANGE_UNKNOWN = 0;
+    /** Distance is very far away from the peer device. */
+    const int RANGE_FAR = 1;
+    /** Distance is relatively long from the peer device, typically a few meters. */
+    const int RANGE_LONG = 2;
+    /** Distance is close to the peer device, typically with one or two meter. */
+    const int RANGE_CLOSE = 3;
+    /** Distance is very close to the peer device, typically within one meter or less. */
+    const int RANGE_WITHIN_REACH = 4;
+
+    /** Invoked by external clients when media device changes are detected. */
+    oneway void nearbyDeviceUpdate(in String routeId, in int rangeZone) = 1;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
similarity index 89%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
rename to packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
index 861a4ed..62b50ed 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.mediattt;
+package com.android.systemui.shared.media;
 
-parcelable DeviceInfo;
+parcelable NearbyDevice;
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
index 96b853f..9cab3ab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.nearby
+package com.android.systemui.shared.media
 
 import android.os.Parcel
 import android.os.Parcelable
@@ -26,14 +26,15 @@
  *   - [routeId] identifying the media route
  *   - [rangeZone] specifying how far away the device with the media route is from this device.
  */
-class NearbyDevice(parcel: Parcel) : Parcelable {
-    var routeId: String? = null
+class NearbyDevice(
+    val routeId: String?,
     @RangeZone val rangeZone: Int
+) : Parcelable {
 
-    init {
-        routeId = parcel.readString() ?: "unknown"
+    private constructor(parcel: Parcel) : this(
+        routeId = parcel.readString() ?: null,
         rangeZone = parcel.readInt()
-    }
+    )
 
     override fun describeContents() = 0
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt
new file mode 100644
index 0000000..b5eaff6
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.media
+
+import androidx.annotation.IntDef
+import kotlin.annotation.AnnotationRetention
+
+@IntDef(
+    INearbyMediaDevicesUpdateCallback.RANGE_UNKNOWN,
+    INearbyMediaDevicesUpdateCallback.RANGE_FAR,
+    INearbyMediaDevicesUpdateCallback.RANGE_LONG,
+    INearbyMediaDevicesUpdateCallback.RANGE_CLOSE,
+    INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
+)
+@Retention(AnnotationRetention.SOURCE)
+/** The various range zones a device can be in, in relation to the current device. */
+annotation class RangeZone
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
deleted file mode 100644
index d41aaf3..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
+++ /dev/null
@@ -1,41 +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.shared.mediattt
-
-import android.os.Parcel
-import android.os.Parcelable
-
-/**
- * Represents a device that can send or receive media. Includes any device information necessary for
- * SysUI to display an informative chip to the user.
- */
-class DeviceInfo(val name: String) : Parcelable {
-    constructor(parcel: Parcel) : this(parcel.readString())
-
-    override fun writeToParcel(dest: Parcel?, flags: Int) {
-        dest?.writeString(name)
-    }
-
-    override fun describeContents() = 0
-
-    override fun toString() = "name: $name"
-
-    companion object CREATOR : Parcelable.Creator<DeviceInfo> {
-        override fun createFromParcel(parcel: Parcel) = DeviceInfo(parcel)
-        override fun newArray(size: Int) = arrayOfNulls<DeviceInfo?>(size)
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl
deleted file mode 100644
index eb1c9d0..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl
+++ /dev/null
@@ -1,133 +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.shared.mediattt;
-
-import android.media.MediaRoute2Info;
-import com.android.systemui.shared.mediattt.DeviceInfo;
-import com.android.systemui.shared.mediattt.IUndoTransferCallback;
-
-/**
- * An interface that can be invoked to trigger media transfer events on System UI.
- *
- * This interface is for the *sender* device, which is the device currently playing media. This
- * sender device can transfer the media to a different device, called the receiver.
- *
- * System UI will implement this interface and other services will invoke it.
- */
-interface IDeviceSenderService {
-    /**
-     * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
-     * the user can potentially *start* a cast to the receiver device if the user moves their device
-     * a bit closer.
-     *
-     * Important notes:
-     *   - When this callback triggers, the device is close enough to inform the user that
-     *     transferring is an option, but the device is *not* close enough to actually initiate a
-     *     transfer yet.
-     *   - This callback is for *starting* a cast. It should be used when this device is currently
-     *     playing media locally and the media should be transferred to be played on the receiver
-     *     device instead.
-     */
-    oneway void closeToReceiverToStartCast(
-        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
-    /**
-     * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
-     * the user can potentially *end* a cast on the receiver device if the user moves this device a
-     * bit closer.
-     *
-     * Important notes:
-     *   - When this callback triggers, the device is close enough to inform the user that
-     *     transferring is an option, but the device is *not* close enough to actually initiate a
-     *     transfer yet.
-     *   - This callback is for *ending* a cast. It should be used when media is currently being
-     *     played on the receiver device and the media should be transferred to play locally
-     *     instead.
-     */
-    oneway void closeToReceiverToEndCast(
-        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
-    /**
-     * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver
-     * device has been started.
-     *
-     * Important notes:
-     *   - This callback is for *starting* a cast. It should be used when this device is currently
-     *     playing media locally and the media has started being transferred to the receiver device
-     *     instead.
-     */
-    oneway void transferToReceiverTriggered(
-        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
-    /**
-     * Invoke to notify System UI that a media transfer from the receiver and back to this device
-     * (the sender) has been started.
-     *
-     * Important notes:
-     *   - This callback is for *ending* a cast. It should be used when media is currently being
-     *     played on the receiver device and the media has started being transferred to play locally
-     *     instead.
-     */
-    oneway void transferToThisDeviceTriggered(
-        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
-    /**
-     * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver
-     * device has finished successfully.
-     *
-     * Important notes:
-     *   - This callback is for *starting* a cast. It should be used when this device had previously
-     *     been playing media locally and the media has successfully been transferred to the
-     *     receiver device instead.
-     *
-     * @param undoCallback will be invoked if the user chooses to undo this transfer.
-     */
-    oneway void transferToReceiverSucceeded(
-        in MediaRoute2Info mediaInfo,
-        in DeviceInfo otherDeviceInfo,
-        in IUndoTransferCallback undoCallback);
-
-    /**
-     * Invoke to notify System UI that a media transfer from the receiver and back to this device
-     * (the sender) has finished successfully.
-     *
-     * Important notes:
-     *   - This callback is for *ending* a cast. It should be used when media was previously being
-     *     played on the receiver device and has been successfully transferred to play locally on
-     *     this device instead.
-     *
-     * @param undoCallback will be invoked if the user chooses to undo this transfer.
-     */
-    oneway void transferToThisDeviceSucceeded(
-        in MediaRoute2Info mediaInfo,
-        in DeviceInfo otherDeviceInfo,
-        in IUndoTransferCallback undoCallback);
-
-    /**
-     * Invoke to notify System UI that the attempted transfer has failed.
-     *
-     * This callback will be used for both the transfer that should've *started* playing the media
-     * on the receiver and the transfer that should've *ended* the playing on the receiver.
-     */
-    oneway void transferFailed(in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
-    /**
-     * Invoke to notify System UI that this device is no longer close to the receiver device.
-     */
-    oneway void noLongerCloseToReceiver(
-        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index cb25e1a..89d6fb5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -17,11 +17,13 @@
 package com.android.keyguard
 
 import android.content.Context
-import android.view.View
 import android.view.ViewGroup
 import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
 import com.android.systemui.unfold.SysUIUnfoldScope
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import javax.inject.Inject
 
@@ -30,84 +32,37 @@
  * the set of ids, which also dictact which direction to move and when, via a filter function.
  */
 @SysUIUnfoldScope
-class KeyguardUnfoldTransition @Inject constructor(
-    val context: Context,
-    val unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
+class KeyguardUnfoldTransition
+@Inject
+constructor(
+    private val context: Context,
+    unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
 ) {
 
-    companion object {
-        final val LEFT = -1
-        final val RIGHT = 1
-    }
+    /** Certain views only need to move if they are not currently centered */
+    var statusViewCentered = false
 
     private val filterSplitShadeOnly = { !statusViewCentered }
     private val filterNever = { true }
 
-    private val ids = setOf(
-        Triple(R.id.keyguard_status_area, LEFT, filterNever),
-        Triple(R.id.controls_button, LEFT, filterNever),
-        Triple(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
-        Triple(R.id.lockscreen_clock_view, LEFT, filterNever),
-        Triple(R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
-        Triple(R.id.wallet_button, RIGHT, filterNever)
-    )
-    private var parent: ViewGroup? = null
-    private var views = listOf<Triple<View, Int, () -> Boolean>>()
-    private var xTranslationMax = 0f
-
-    /**
-     * Certain views only need to move if they are not currently centered
-     */
-    var statusViewCentered = false
-
-    init {
-        unfoldProgressProvider.addCallback(
-            object : TransitionProgressListener {
-                override fun onTransitionStarted() {
-                    findViews()
-                }
-
-                override fun onTransitionProgress(progress: Float) {
-                    translateViews(progress)
-                }
-
-                override fun onTransitionFinished() {
-                    translateViews(1f)
-                }
-            }
-        )
+    private val translateAnimator by lazy {
+        UnfoldConstantTranslateAnimator(
+            viewsIdToTranslate =
+                setOf(
+                    ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever),
+                    ViewIdToTranslate(R.id.controls_button, LEFT, filterNever),
+                    ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
+                    ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
+                    ViewIdToTranslate(
+                        R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
+                    ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)),
+            progressProvider = unfoldProgressProvider)
     }
 
-    /**
-     * Relies on the [parent] to locate views to translate
-     */
+    /** Relies on the [parent] to locate views to translate. */
     fun setup(parent: ViewGroup) {
-        this.parent = parent
-        xTranslationMax = context.resources.getDimensionPixelSize(
-            R.dimen.keyguard_unfold_translation_x).toFloat()
-    }
-
-    /**
-     * Manually translate views based on set direction. At the moment
-     * [UnfoldMoveFromCenterAnimator] exists but moves all views a dynamic distance
-     * from their mid-point. This code instead will only ever translate by a fixed amount.
-     */
-    private fun translateViews(progress: Float) {
-        val xTrans = progress * xTranslationMax - xTranslationMax
-        views.forEach {
-            (view, direction, pred) -> if (pred()) {
-                view.setTranslationX(xTrans * direction)
-            }
-        }
-    }
-
-    private fun findViews() {
-        parent?.let { p ->
-            views = ids.mapNotNull {
-                (id, direction, pred) -> p.findViewById<View>(id)?.let {
-                    Triple(it, direction, pred)
-                }
-            }
-        }
+        val translationMax =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
+        translateAnimator.init(parent, translationMax)
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f2d0427..cc10b02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1196,6 +1196,21 @@
         return fingerprintAllowed || faceAllowed;
     }
 
+    /**
+     * Returns whether the user is unlocked with a biometric that is currently bypassing
+     * the lock screen.
+     */
+    public boolean getUserUnlockedWithBiometricAndIsBypassing(int userId) {
+        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
+        BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+        // fingerprint always bypasses
+        boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
+                && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
+        boolean faceAllowed = face != null && face.mAuthenticated
+                && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
+        return fingerprintAllowed || faceAllowed && mKeyguardBypassController.canBypass();
+    }
+
     public boolean getUserTrustIsManaged(int userId) {
         return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 08ed24c..b32c2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -49,6 +49,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.hdmi.HdmiCecSetMenuLanguageHelper;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -250,6 +251,7 @@
     @Inject Lazy<LocationController> mLocationController;
     @Inject Lazy<RotationLockController> mRotationLockController;
     @Inject Lazy<ZenModeController> mZenModeController;
+    @Inject Lazy<HdmiCecSetMenuLanguageHelper> mHdmiCecSetMenuLanguageHelper;
     @Inject Lazy<HotspotController> mHotspotController;
     @Inject Lazy<CastController> mCastController;
     @Inject Lazy<FlashlightController> mFlashlightController;
diff --git a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
index fdc3229..2b8d3e0 100644
--- a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
@@ -54,11 +54,11 @@
                 Utils.getThemeAttr(context, R.attr.lightIconTheme))
         darkColor = Color(
                 Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor),
-                Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.backgroundColor),
+                Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor),
                 Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor))
         lightColor = Color(
                 Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor),
-                Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.backgroundColor),
+                Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor),
                 Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor))
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 20d6e32..881e6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -120,6 +120,13 @@
             AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; // = 9
 
     /**
+     * Action ID to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
+     * play/stop media
+     */
+    private static final int SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK =
+            AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK; // = 10
+
+    /**
      * Action ID to trigger the accessibility button
      */
     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON =
@@ -137,6 +144,36 @@
     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
             AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE; // 15
 
+    /**
+     * Action ID to trigger the dpad up button
+     */
+    private static final int SYSTEM_ACTION_ID_DPAD_UP =
+            AccessibilityService.GLOBAL_ACTION_DPAD_UP; // 16
+
+    /**
+     * Action ID to trigger the dpad down button
+     */
+    private static final int SYSTEM_ACTION_ID_DPAD_DOWN =
+            AccessibilityService.GLOBAL_ACTION_DPAD_DOWN; // 17
+
+    /**
+     * Action ID to trigger the dpad left button
+     */
+    private static final int SYSTEM_ACTION_ID_DPAD_LEFT =
+            AccessibilityService.GLOBAL_ACTION_DPAD_LEFT; // 18
+
+    /**
+     * Action ID to trigger the dpad right button
+     */
+    private static final int SYSTEM_ACTION_ID_DPAD_RIGHT =
+            AccessibilityService.GLOBAL_ACTION_DPAD_RIGHT; // 19
+
+    /**
+     * Action ID to trigger dpad center keyevent
+     */
+    private static final int SYSTEM_ACTION_ID_DPAD_CENTER =
+            AccessibilityService.GLOBAL_ACTION_DPAD_CENTER; // 20
+
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
     private final SystemActionsBroadcastReceiver mReceiver;
@@ -222,10 +259,34 @@
                 R.string.accessibility_system_action_screenshot_label,
                 SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT);
 
+        RemoteAction actionHeadsetHook = createRemoteAction(
+                R.string.accessibility_system_action_headset_hook_label,
+                SystemActionsBroadcastReceiver.INTENT_ACTION_HEADSET_HOOK);
+
         RemoteAction actionAccessibilityShortcut = createRemoteAction(
                 R.string.accessibility_system_action_hardware_a11y_shortcut_label,
                 SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
 
+        RemoteAction actionDpadUp = createRemoteAction(
+                R.string.accessibility_system_action_dpad_up_label,
+                SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_UP);
+
+        RemoteAction actionDpadDown = createRemoteAction(
+                R.string.accessibility_system_action_dpad_down_label,
+                SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_DOWN);
+
+        RemoteAction actionDpadLeft = createRemoteAction(
+                R.string.accessibility_system_action_dpad_left_label,
+                SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_LEFT);
+
+        RemoteAction actionDpadRight = createRemoteAction(
+                R.string.accessibility_system_action_dpad_right_label,
+                SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_RIGHT);
+
+        RemoteAction actionDpadCenter = createRemoteAction(
+                R.string.accessibility_system_action_dpad_center_label,
+                SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER);
+
         mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
         mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
         mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
@@ -234,8 +295,14 @@
         mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
         mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
         mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
+        mA11yManager.registerSystemAction(actionHeadsetHook, SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK);
         mA11yManager.registerSystemAction(
                 actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT);
+        mA11yManager.registerSystemAction(actionDpadUp, SYSTEM_ACTION_ID_DPAD_UP);
+        mA11yManager.registerSystemAction(actionDpadDown, SYSTEM_ACTION_ID_DPAD_DOWN);
+        mA11yManager.registerSystemAction(actionDpadLeft, SYSTEM_ACTION_ID_DPAD_LEFT);
+        mA11yManager.registerSystemAction(actionDpadRight, SYSTEM_ACTION_ID_DPAD_RIGHT);
+        mA11yManager.registerSystemAction(actionDpadCenter, SYSTEM_ACTION_ID_DPAD_CENTER);
         registerOrUnregisterDismissNotificationShadeAction();
     }
 
@@ -305,6 +372,10 @@
                 labelId = R.string.accessibility_system_action_screenshot_label;
                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT;
                 break;
+            case SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK:
+                labelId = R.string.accessibility_system_action_headset_hook_label;
+                intent = SystemActionsBroadcastReceiver.INTENT_ACTION_HEADSET_HOOK;
+                break;
             case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON:
                 labelId = R.string.accessibility_system_action_on_screen_a11y_shortcut_label;
                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON;
@@ -323,6 +394,26 @@
                 intent = SystemActionsBroadcastReceiver
                         .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE;
                 break;
+            case SYSTEM_ACTION_ID_DPAD_UP:
+                labelId = R.string.accessibility_system_action_dpad_up_label;
+                intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_UP;
+                break;
+            case SYSTEM_ACTION_ID_DPAD_DOWN:
+                labelId = R.string.accessibility_system_action_dpad_down_label;
+                intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_DOWN;
+                break;
+            case SYSTEM_ACTION_ID_DPAD_LEFT:
+                labelId = R.string.accessibility_system_action_dpad_left_label;
+                intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_LEFT;
+                break;
+            case SYSTEM_ACTION_ID_DPAD_RIGHT:
+                labelId = R.string.accessibility_system_action_dpad_right_label;
+                intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_RIGHT;
+                break;
+            case SYSTEM_ACTION_ID_DPAD_CENTER:
+                labelId = R.string.accessibility_system_action_dpad_center_label;
+                intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER;
+                break;
             default:
                 return;
         }
@@ -411,6 +502,10 @@
                 SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
     }
 
+    private void handleHeadsetHook() {
+        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
+    }
+
     private void handleAccessibilityButton() {
         AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked(
                 Display.DEFAULT_DISPLAY);
@@ -434,6 +529,26 @@
                         CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
     }
 
+    private void handleDpadUp() {
+        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_UP);
+    }
+
+    private void handleDpadDown() {
+        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_DOWN);
+    }
+
+    private void handleDpadLeft() {
+        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
+    }
+
+    private void handleDpadRight() {
+        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT);
+    }
+
+    private void handleDpadCenter() {
+        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
+    }
+
     private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
         private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
         private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
@@ -443,6 +558,7 @@
         private static final String INTENT_ACTION_POWER_DIALOG = "SYSTEM_ACTION_POWER_DIALOG";
         private static final String INTENT_ACTION_LOCK_SCREEN = "SYSTEM_ACTION_LOCK_SCREEN";
         private static final String INTENT_ACTION_TAKE_SCREENSHOT = "SYSTEM_ACTION_TAKE_SCREENSHOT";
+        private static final String INTENT_ACTION_HEADSET_HOOK = "SYSTEM_ACTION_HEADSET_HOOK";
         private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON =
                 "SYSTEM_ACTION_ACCESSIBILITY_BUTTON";
         private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER =
@@ -451,6 +567,11 @@
                 "SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT";
         private static final String INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
                 "SYSTEM_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE";
+        private static final String INTENT_ACTION_DPAD_UP = "SYSTEM_ACTION_DPAD_UP";
+        private static final String INTENT_ACTION_DPAD_DOWN = "SYSTEM_ACTION_DPAD_DOWN";
+        private static final String INTENT_ACTION_DPAD_LEFT = "SYSTEM_ACTION_DPAD_LEFT";
+        private static final String INTENT_ACTION_DPAD_RIGHT = "SYSTEM_ACTION_DPAD_RIGHT";
+        private static final String INTENT_ACTION_DPAD_CENTER = "SYSTEM_ACTION_DPAD_CENTER";
 
         private PendingIntent createPendingIntent(Context context, String intentAction) {
             switch (intentAction) {
@@ -462,10 +583,16 @@
                 case INTENT_ACTION_POWER_DIALOG:
                 case INTENT_ACTION_LOCK_SCREEN:
                 case INTENT_ACTION_TAKE_SCREENSHOT:
+                case INTENT_ACTION_HEADSET_HOOK:
                 case INTENT_ACTION_ACCESSIBILITY_BUTTON:
                 case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
                 case INTENT_ACTION_ACCESSIBILITY_SHORTCUT:
-                case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
+                case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE:
+                case INTENT_ACTION_DPAD_UP:
+                case INTENT_ACTION_DPAD_DOWN:
+                case INTENT_ACTION_DPAD_LEFT:
+                case INTENT_ACTION_DPAD_RIGHT:
+                case INTENT_ACTION_DPAD_CENTER: {
                     Intent intent = new Intent(intentAction);
                     intent.setPackage(context.getPackageName());
                     return PendingIntent.getBroadcast(context, 0, intent,
@@ -487,10 +614,16 @@
             intentFilter.addAction(INTENT_ACTION_POWER_DIALOG);
             intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN);
             intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT);
+            intentFilter.addAction(INTENT_ACTION_HEADSET_HOOK);
             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+            intentFilter.addAction(INTENT_ACTION_DPAD_UP);
+            intentFilter.addAction(INTENT_ACTION_DPAD_DOWN);
+            intentFilter.addAction(INTENT_ACTION_DPAD_LEFT);
+            intentFilter.addAction(INTENT_ACTION_DPAD_RIGHT);
+            intentFilter.addAction(INTENT_ACTION_DPAD_CENTER);
             return intentFilter;
         }
 
@@ -530,6 +663,10 @@
                     handleTakeScreenshot();
                     break;
                 }
+                case INTENT_ACTION_HEADSET_HOOK: {
+                    handleHeadsetHook();
+                    break;
+                }
                 case INTENT_ACTION_ACCESSIBILITY_BUTTON: {
                     handleAccessibilityButton();
                     break;
@@ -546,6 +683,26 @@
                     handleAccessibilityDismissNotificationShade();
                     break;
                 }
+                case INTENT_ACTION_DPAD_UP: {
+                    handleDpadUp();
+                    break;
+                }
+                case INTENT_ACTION_DPAD_DOWN: {
+                    handleDpadDown();
+                    break;
+                }
+                case INTENT_ACTION_DPAD_LEFT: {
+                    handleDpadLeft();
+                    break;
+                }
+                case INTENT_ACTION_DPAD_RIGHT: {
+                    handleDpadRight();
+                    break;
+                }
+                case INTENT_ACTION_DPAD_CENTER: {
+                    handleDpadCenter();
+                    break;
+                }
                 default:
                     break;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b0f7e55..fe5e36e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -37,6 +37,7 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
@@ -64,6 +65,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.concurrency.Execution;
 
@@ -96,6 +98,7 @@
     private final Handler mHandler;
     private final Execution mExecution;
     private final CommandQueue mCommandQueue;
+    private final StatusBarStateController mStatusBarStateController;
     private final ActivityTaskManager mActivityTaskManager;
     @Nullable
     private final FingerprintManager mFingerprintManager;
@@ -118,6 +121,7 @@
     @Nullable private UdfpsController mUdfpsController;
     @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
     @Nullable private SidefpsController mSidefpsController;
+    @Nullable private IBiometricContextListener mBiometricContextListener;
     @VisibleForTesting
     TaskStackListener mTaskStackListener;
     @VisibleForTesting
@@ -130,7 +134,7 @@
     @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
 
     @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
-    private SensorPrivacyManager mSensorPrivacyManager;
+    @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
 
     private class BiometricTaskStackListener extends TaskStackListener {
@@ -491,6 +495,7 @@
             Provider<SidefpsController> sidefpsControllerFactory,
             @NonNull DisplayManager displayManager,
             WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull StatusBarStateController statusBarStateController,
             @Main Handler handler) {
         super(context);
         mExecution = execution;
@@ -504,6 +509,7 @@
         mSidefpsControllerFactory = sidefpsControllerFactory;
         mWindowManager = windowManager;
         mUdfpsEnrolledForUser = new SparseBooleanArray();
+
         mOrientationListener = new BiometricDisplayListener(
                 context,
                 displayManager,
@@ -514,6 +520,14 @@
                     return Unit.INSTANCE;
                 });
 
+        mStatusBarStateController = statusBarStateController;
+        mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+            @Override
+            public void onDozingChanged(boolean isDozing) {
+                notifyDozeChanged(isDozing);
+            }
+        });
+
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
 
         int[] faceAuthLocation = context.getResources().getIntArray(
@@ -564,6 +578,22 @@
         mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
     }
 
+    @Override
+    public void setBiometicContextListener(IBiometricContextListener listener) {
+        mBiometricContextListener = listener;
+        notifyDozeChanged(mStatusBarStateController.isDozing());
+    }
+
+    private void notifyDozeChanged(boolean isDozing) {
+        if (mBiometricContextListener != null) {
+            try {
+                mBiometricContextListener.onDozeChanged(isDozing);
+            } catch (RemoteException e) {
+                Log.w(TAG, "failed to notify initial doze state");
+            }
+        }
+    }
+
     /**
      * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 0e1cd51..72b40d4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -63,7 +63,8 @@
             mClipboardOverlayController =
                     new ClipboardOverlayController(mContext, new TimeoutHandler(mContext));
         }
-        mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip());
+        mClipboardOverlayController.setClipData(
+                mClipboardManager.getPrimaryClip(), mClipboardManager.getPrimaryClipSource());
         mClipboardOverlayController.setOnSessionCompleteListener(() -> {
             // Session is complete, free memory until it's needed again.
             mClipboardOverlayController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index b6bcb87..8b549b4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -74,7 +74,7 @@
 import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.R;
 import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.ScreenshotActionChip;
+import com.android.systemui.screenshot.OverlayActionChip;
 import com.android.systemui.screenshot.TimeoutHandler;
 
 import java.io.IOException;
@@ -106,12 +106,12 @@
     private final DraggableConstraintLayout mView;
     private final ImageView mImagePreview;
     private final TextView mTextPreview;
-    private final ScreenshotActionChip mEditChip;
-    private final ScreenshotActionChip mRemoteCopyChip;
+    private final OverlayActionChip mEditChip;
+    private final OverlayActionChip mRemoteCopyChip;
     private final View mActionContainerBackground;
     private final View mDismissButton;
     private final LinearLayout mActionContainer;
-    private final ArrayList<ScreenshotActionChip> mActionChips = new ArrayList<>();
+    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
 
     private Runnable mOnSessionCompleteListener;
 
@@ -182,6 +182,7 @@
         withWindowAttached(() -> {
             mWindow.setContentView(mView);
             updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+            mView.requestLayout();
             mView.post(this::animateIn);
         });
 
@@ -213,7 +214,7 @@
         mContext.sendBroadcast(new Intent(COPY_OVERLAY_ACTION), SELF_PERMISSION);
     }
 
-    void setClipData(ClipData clipData) {
+    void setClipData(ClipData clipData, String clipSource) {
         reset();
         if (clipData == null || clipData.getItemCount() == 0) {
             showTextPreview(mContext.getResources().getString(
@@ -221,7 +222,7 @@
         } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
             ClipData.Item item = clipData.getItemAt(0);
             if (item.getTextLinks() != null) {
-                AsyncTask.execute(() -> classifyText(clipData.getItemAt(0)));
+                AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
             }
             showEditableText(item.getText());
         } else if (clipData.getItemAt(0).getUri() != null) {
@@ -238,7 +239,7 @@
         mOnSessionCompleteListener = runnable;
     }
 
-    private void classifyText(ClipData.Item item) {
+    private void classifyText(ClipData.Item item, String source) {
         ArrayList<RemoteAction> actions = new ArrayList<>();
         for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
             TextClassification classification = mTextClassifier.classifyText(
@@ -246,21 +247,21 @@
             actions.addAll(classification.getActions());
         }
         mView.post(() -> {
-            for (ScreenshotActionChip chip : mActionChips) {
-                mActionContainer.removeView(chip);
-            }
-            mActionChips.clear();
+            resetActionChips();
             for (RemoteAction action : actions) {
-                ScreenshotActionChip chip = constructActionChip(action);
-                mActionContainer.addView(chip);
-                mActionChips.add(chip);
+                Intent targetIntent = action.getActionIntent().getIntent();
+                if (!TextUtils.equals(source, targetIntent.getComponent().getPackageName())) {
+                    OverlayActionChip chip = constructActionChip(action);
+                    mActionContainer.addView(chip);
+                    mActionChips.add(chip);
+                }
             }
         });
     }
 
-    private ScreenshotActionChip constructActionChip(RemoteAction action) {
-        ScreenshotActionChip chip = (ScreenshotActionChip) LayoutInflater.from(mContext).inflate(
-                R.layout.screenshot_action_chip, mActionContainer, false);
+    private OverlayActionChip constructActionChip(RemoteAction action) {
+        OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+                R.layout.overlay_action_chip, mActionContainer, false);
         chip.setText(action.getTitle());
         chip.setIcon(action.getIcon(), false);
         chip.setPendingIntent(action.getActionIntent(), this::animateOut);
@@ -340,7 +341,7 @@
         mEditChip.setAlpha(1f);
         ContentResolver resolver = mContext.getContentResolver();
         try {
-            int size = mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale);
+            int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
             // The width of the view is capped, height maintains aspect ratio, so allow it to be
             // taller if needed.
             Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
@@ -364,7 +365,7 @@
     }
 
     private void animateOut() {
-        getExitAnimation().start();
+        mView.dismiss();
     }
 
     private ValueAnimator getEnterAnimation() {
@@ -400,28 +401,6 @@
         return anim;
     }
 
-    private ValueAnimator getExitAnimation() {
-        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-
-        anim.addUpdateListener(animation -> {
-            mView.setAlpha(1 - animation.getAnimatedFraction());
-            final View actionBackground = requireNonNull(
-                    mView.findViewById(R.id.actions_container_background));
-            mView.setTranslationX(
-                    -animation.getAnimatedFraction() * actionBackground.getWidth() / 2);
-        });
-
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                hideImmediate();
-            }
-        });
-
-        return anim;
-    }
-
     private void hideImmediate() {
         // Note this may be called multiple times if multiple dismissal events happen at the same
         // time.
@@ -451,13 +430,17 @@
         }
     }
 
-    private void reset() {
-        mView.setTranslationX(0);
-        mView.setAlpha(0);
-        for (ScreenshotActionChip chip : mActionChips) {
+    private void resetActionChips() {
+        for (OverlayActionChip chip : mActionChips) {
             mActionContainer.removeView(chip);
         }
         mActionChips.clear();
+    }
+
+    private void reset() {
+        mView.setTranslationX(0);
+        mView.setAlpha(0);
+        resetActionChips();
         mTimeoutHandler.cancelTimeout();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
index 6a4be6e..8843462 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
@@ -98,10 +98,23 @@
         return mSwipeDetector.onTouchEvent(ev);
     }
 
+    /**
+     * Dismiss the view, with animation controlled by SwipeDismissHandler
+     */
+    public void dismiss() {
+        mSwipeDismissHandler.dismiss();
+    }
+
+    /**
+     * Set the callback to be run after view is dismissed
+     */
     public void setOnDismissCallback(Runnable callback) {
         mOnDismiss = callback;
     }
 
+    /**
+     * Set the callback to be run when the view is interacted with (e.g. tapped)
+     */
     public void setOnInteractionCallback(Runnable callback) {
         mOnInteraction = callback;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index bce8784..1653e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 
 import com.android.systemui.ForegroundServicesDialog;
+import com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity;
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -120,4 +121,11 @@
     @IntoMap
     @ClassKey(TvUnblockSensorActivity.class)
     public abstract Activity bindTvUnblockSensorActivity(TvUnblockSensorActivity activity);
+
+    /** Inject into HdmiCecSetMenuLanguageActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(HdmiCecSetMenuLanguageActivity.class)
+    public abstract Activity bindHdmiCecSetMenuLanguageActivity(
+            HdmiCecSetMenuLanguageActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 96e2302..ec2beb1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -26,10 +26,15 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.clipboardoverlay.ClipboardListener;
 import com.android.systemui.dreams.DreamOverlayRegistrant;
+import com.android.systemui.dreams.SmartSpaceComplication;
+import com.android.systemui.dreams.complication.DreamClockDateComplication;
+import com.android.systemui.dreams.complication.DreamClockTimeComplication;
+import com.android.systemui.dreams.complication.DreamWeatherComplication;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.log.SessionTracker;
+import com.android.systemui.media.dream.MediaDreamSentinel;
 import com.android.systemui.media.systemsounds.HomeSoundEffectController;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
@@ -218,4 +223,39 @@
     @ClassKey(DreamOverlayRegistrant.class)
     public abstract CoreStartable bindDreamOverlayRegistrant(
             DreamOverlayRegistrant dreamOverlayRegistrant);
+
+    /** Inject into SmartSpaceComplication.Registrant */
+    @Binds
+    @IntoMap
+    @ClassKey(SmartSpaceComplication.Registrant.class)
+    public abstract CoreStartable bindSmartSpaceComplicationRegistrant(
+            SmartSpaceComplication.Registrant registrant);
+
+    /** Inject into MediaDreamSentinel. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaDreamSentinel.class)
+    public abstract CoreStartable bindMediaDreamSentinel(
+            MediaDreamSentinel sentinel);
+
+    /** Inject into DreamClockTimeComplication.Registrant */
+    @Binds
+    @IntoMap
+    @ClassKey(DreamClockTimeComplication.Registrant.class)
+    public abstract CoreStartable bindDreamClockTimeComplicationRegistrant(
+            DreamClockTimeComplication.Registrant registrant);
+
+    /** Inject into DreamClockDateComplication.Registrant */
+    @Binds
+    @IntoMap
+    @ClassKey(DreamClockDateComplication.Registrant.class)
+    public abstract CoreStartable bindDreamClockDateComplicationRegistrant(
+            DreamClockDateComplication.Registrant registrant);
+
+    /** Inject into DreamWeatherComplication.Registrant */
+    @Binds
+    @IntoMap
+    @ClassKey(DreamWeatherComplication.Registrant.class)
+    public abstract CoreStartable bindDreamWeatherComplicationRegistrant(
+            DreamWeatherComplication.Registrant registrant);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 3ee0cad..2160744 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -88,16 +88,20 @@
                 }
             };
 
+    private DreamOverlayStateController mStateController;
+
     @Inject
     public DreamOverlayService(
             Context context,
             @Main Executor executor,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
+            DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mContext = context;
         mExecutor = executor;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
+        mStateController = stateController;
 
         final DreamOverlayComponent component =
                 dreamOverlayComponentFactory.create(mViewModelStore, mHost);
@@ -118,6 +122,7 @@
         setCurrentState(Lifecycle.State.DESTROYED);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         windowManager.removeView(mWindow.getDecorView());
+        mStateController.setOverlayActive(false);
         super.onDestroy();
     }
 
@@ -127,6 +132,7 @@
         mExecutor.execute(() -> {
             addOverlayWindowLocked(layoutParams);
             setCurrentState(Lifecycle.State.RESUMED);
+            mStateController.setOverlayActive(true);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index e838848..ac7457d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dreams;
 
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -30,6 +32,8 @@
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -41,6 +45,16 @@
 @SysUISingleton
 public class DreamOverlayStateController implements
         CallbackController<DreamOverlayStateController.Callback> {
+    private static final String TAG = "DreamOverlayStateCtlr";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
+
+    private static final int OP_CLEAR_STATE = 1;
+    private static final int OP_SET_STATE = 2;
+
+    private int mState;
+
     /**
      * Callback for dream overlay events.
      */
@@ -50,11 +64,26 @@
          */
         default void onComplicationsChanged() {
         }
+
+        /**
+         * Called when the dream overlay state changes.
+         */
+        default void onStateChanged() {
+        }
+
+        /**
+         * Called when the available complication types changes.
+         */
+        default void onAvailableComplicationTypesChanged() {
+        }
     }
 
     private final Executor mExecutor;
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
 
+    @Complication.ComplicationType
+    private int mAvailableComplicationTypes = Complication.COMPLICATION_TYPE_NONE;
+
     private final Collection<Complication> mComplications = new HashSet();
 
     @VisibleForTesting
@@ -89,7 +118,33 @@
      * Returns collection of present {@link Complication}.
      */
     public Collection<Complication> getComplications() {
-        return Collections.unmodifiableCollection(mComplications);
+        return getComplications(true);
+    }
+
+    /**
+     * Returns collection of present {@link Complication}.
+     */
+    public Collection<Complication> getComplications(boolean filterByAvailability) {
+        return Collections.unmodifiableCollection(filterByAvailability
+                ? mComplications
+                .stream()
+                .filter(complication -> {
+                    @Complication.ComplicationType
+                    final int requiredTypes = complication.getRequiredTypeAvailability();
+
+                    return requiredTypes == Complication.COMPLICATION_TYPE_NONE
+                            || (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
+                })
+                .collect(Collectors.toCollection(HashSet::new))
+                : mComplications);
+    }
+
+    private void notifyCallbacks(Consumer<Callback> callbackConsumer) {
+        mExecutor.execute(() -> {
+            for (Callback callback : mCallbacks) {
+                callbackConsumer.accept(callback);
+            }
+        });
     }
 
     @Override
@@ -117,4 +172,58 @@
             mCallbacks.remove(callback);
         });
     }
+
+    /**
+     * Returns whether the overlay is active.
+     * @return {@code true} if overlay is active, {@code false} otherwise.
+     */
+    public boolean isOverlayActive() {
+        return containsState(STATE_DREAM_OVERLAY_ACTIVE);
+    }
+
+    private boolean containsState(int state) {
+        return (mState & state) != 0;
+    }
+
+    private void modifyState(int op, int state) {
+        final int existingState = mState;
+        switch (op) {
+            case OP_CLEAR_STATE:
+                mState &= ~state;
+                break;
+            case OP_SET_STATE:
+                mState |= state;
+                break;
+        }
+
+        if (existingState != mState) {
+            notifyCallbacks(callback -> callback.onStateChanged());
+        }
+    }
+
+    /**
+     * Sets whether the overlay is active.
+     * @param active {@code true} if overlay is active, {@code false} otherwise.
+     */
+    public void setOverlayActive(boolean active) {
+        modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
+    }
+
+    /**
+     * Returns the available complication types.
+     */
+    @Complication.ComplicationType
+    public int getAvailableComplicationTypes() {
+        return mAvailableComplicationTypes;
+    }
+
+    /**
+     * Sets the available complication types for the dream overlay.
+     */
+    public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
+        mExecutor.execute(() -> {
+            mAvailableComplicationTypes = types;
+            mCallbacks.forEach(callback -> callback.onAvailableComplicationTypesChanged());
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
new file mode 100644
index 0000000..09221b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import javax.inject.Inject;
+
+/**
+ * {@link SmartSpaceComplication} embodies the SmartSpace view found on the lockscreen as a
+ * {@link Complication}
+ */
+public class SmartSpaceComplication implements Complication {
+    /**
+     * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
+     * SystemUI.
+     */
+    public static class Registrant extends CoreStartable {
+        private final LockscreenSmartspaceController mSmartSpaceController;
+        private final DreamOverlayStateController mDreamOverlayStateController;
+        private final SmartSpaceComplication mComplication;
+
+        /**
+         * Default constructor for {@link SmartSpaceComplication}.
+         */
+        @Inject
+        public Registrant(Context context,
+                DreamOverlayStateController dreamOverlayStateController,
+                SmartSpaceComplication smartSpaceComplication,
+                LockscreenSmartspaceController smartSpaceController) {
+            super(context);
+            mDreamOverlayStateController = dreamOverlayStateController;
+            mComplication = smartSpaceComplication;
+            mSmartSpaceController = smartSpaceController;
+        }
+
+        @Override
+        public void start() {
+            if (mSmartSpaceController.isEnabled()) {
+                mDreamOverlayStateController.addComplication(mComplication);
+            }
+        }
+    }
+
+    private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+        private final LockscreenSmartspaceController mSmartSpaceController;
+        private final Context mContext;
+
+        protected SmartSpaceComplicationViewHolder(
+                Context context,
+                LockscreenSmartspaceController smartSpaceController) {
+            mSmartSpaceController = smartSpaceController;
+            mContext = context;
+        }
+
+        @Override
+        public View getView() {
+            final FrameLayout smartSpaceContainer = new FrameLayout(mContext);
+            smartSpaceContainer.addView(
+                    mSmartSpaceController.buildAndConnectView(smartSpaceContainer),
+                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.WRAP_CONTENT));
+
+            return smartSpaceContainer;
+        }
+
+        @Override
+        public ComplicationLayoutParams getLayoutParams() {
+            return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
+                    ComplicationLayoutParams.DIRECTION_DOWN,
+                    0, true);
+        }
+    }
+
+    private final LockscreenSmartspaceController mSmartSpaceController;
+    private final Context mContext;
+
+    @Inject
+    public SmartSpaceComplication(Context context,
+            LockscreenSmartspaceController smartSpaceController) {
+        mContext = context;
+        mSmartSpaceController = smartSpaceController;
+    }
+
+    @Override
+    public ViewHolder createView(ComplicationViewModel model) {
+        return new SmartSpaceComplicationViewHolder(mContext, mSmartSpaceController);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 96cf50d..fe458f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -154,6 +154,27 @@
     int CATEGORY_SYSTEM = 1 << 1;
 
     /**
+     * The type of dream complications which can be provided by a {@link Complication}.
+     */
+    @IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = {
+            COMPLICATION_TYPE_NONE,
+            COMPLICATION_TYPE_TIME,
+            COMPLICATION_TYPE_DATE,
+            COMPLICATION_TYPE_WEATHER,
+            COMPLICATION_TYPE_AIR_QUALITY,
+            COMPLICATION_TYPE_CAST_INFO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ComplicationType {}
+
+    int COMPLICATION_TYPE_NONE = 0;
+    int COMPLICATION_TYPE_TIME = 1;
+    int COMPLICATION_TYPE_DATE = 1 << 1;
+    int COMPLICATION_TYPE_WEATHER = 1 << 2;
+    int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
+    int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
+
+    /**
      * The {@link Host} interface specifies a way a {@link Complication} to communicate with its
      * parent entity for information and actions.
      */
@@ -207,4 +228,15 @@
      * @return a {@link ViewHolder} for this {@link Complication} instance.
      */
     ViewHolder createView(ComplicationViewModel model);
+
+    /**
+     * Returns the types that must be present in order for this complication to participate on
+     * the dream overlay. By default, this method returns
+     * {@code Complication.COMPLICATION_TYPE_NONE} to indicate no types are required.
+     * @return
+     */
+    @Complication.ComplicationType
+    default int getRequiredTypeAvailability() {
+        return Complication.COMPLICATION_TYPE_NONE;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
index 76818fa..f6fe8d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
@@ -42,6 +42,10 @@
                 setValue(mDreamOverlayStateController.getComplications());
             }
 
+            @Override
+            public void onAvailableComplicationTypesChanged() {
+                setValue(mDreamOverlayStateController.getComplications());
+            }
         };
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index cb24ae6..5223f37 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -25,10 +25,13 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.Constraints;
 
+import com.android.systemui.R;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -102,6 +105,8 @@
 
             final int direction = getLayoutParams().getDirection();
 
+            final boolean snapsToGuide = getLayoutParams().snapsToGuide();
+
             // If no parent, view is the anchor. In this case, it is given the highest priority for
             // alignment. All alignment preferences are done in relation to the parent container.
             final boolean isRoot = head == mView;
@@ -125,6 +130,11 @@
                         } else {
                             params.startToEnd = head.getId();
                         }
+                        if (snapsToGuide
+                                && (direction == ComplicationLayoutParams.DIRECTION_DOWN
+                                || direction == ComplicationLayoutParams.DIRECTION_UP)) {
+                            params.endToStart = R.id.complication_start_guide;
+                        }
                         break;
                     case ComplicationLayoutParams.POSITION_TOP:
                         if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) {
@@ -132,6 +142,11 @@
                         } else {
                             params.topToBottom = head.getId();
                         }
+                        if (snapsToGuide
+                                && (direction == ComplicationLayoutParams.DIRECTION_END
+                                || direction == ComplicationLayoutParams.DIRECTION_START)) {
+                            params.endToStart = R.id.complication_top_guide;
+                        }
                         break;
                     case ComplicationLayoutParams.POSITION_BOTTOM:
                         if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) {
@@ -139,6 +154,11 @@
                         } else {
                             params.bottomToTop = head.getId();
                         }
+                        if (snapsToGuide
+                                && (direction == ComplicationLayoutParams.DIRECTION_END
+                                || direction == ComplicationLayoutParams.DIRECTION_START)) {
+                            params.topToBottom = R.id.complication_bottom_guide;
+                        }
                         break;
                     case ComplicationLayoutParams.POSITION_END:
                         if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) {
@@ -146,6 +166,11 @@
                         } else {
                             params.endToStart = head.getId();
                         }
+                        if (snapsToGuide
+                                && (direction == ComplicationLayoutParams.DIRECTION_UP
+                                || direction == ComplicationLayoutParams.DIRECTION_DOWN)) {
+                            params.startToEnd = R.id.complication_end_guide;
+                        }
                         break;
                 }
             });
@@ -153,6 +178,16 @@
             mView.setLayoutParams(params);
         }
 
+        private void setGuide(ConstraintLayout.LayoutParams lp, int validDirections,
+                Consumer<ConstraintLayout.LayoutParams> consumer) {
+            final ComplicationLayoutParams layoutParams = getLayoutParams();
+            if (!layoutParams.snapsToGuide()) {
+                return;
+            }
+
+            consumer.accept(lp);
+        }
+
         /**
          * Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from
          * being shown further.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index f9a69fa..8e8cb72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -40,13 +40,13 @@
 
     @interface Position {}
     /** Align view with the top of parent or bottom of preceding {@link Complication}. */
-    static final int POSITION_TOP = 1 << 0;
+    public static final int POSITION_TOP = 1 << 0;
     /** Align view with the bottom of parent or top of preceding {@link Complication}. */
-    static final int POSITION_BOTTOM = 1 << 1;
+    public static final int POSITION_BOTTOM = 1 << 1;
     /** Align view with the start of parent or end of preceding {@link Complication}. */
-    static final int POSITION_START = 1 << 2;
+    public static final int POSITION_START = 1 << 2;
     /** Align view with the end of parent or start of preceding {@link Complication}. */
-    static final int POSITION_END = 1 << 3;
+    public static final int POSITION_END = 1 << 3;
 
     private static final int FIRST_POSITION = POSITION_TOP;
     private static final int LAST_POSITION = POSITION_END;
@@ -61,13 +61,13 @@
 
     @interface Direction {}
     /** Position view upward from position. */
-    static final int DIRECTION_UP = 1 << 0;
+    public static final int DIRECTION_UP = 1 << 0;
     /** Position view downward from position. */
-    static final int DIRECTION_DOWN = 1 << 1;
+    public static final int DIRECTION_DOWN = 1 << 1;
     /** Position view towards the start of the parent. */
-    static final int DIRECTION_START = 1 << 2;
+    public static final int DIRECTION_START = 1 << 2;
     /** Position view towards the end of parent. */
-    static final int DIRECTION_END = 1 << 3;
+    public static final int DIRECTION_END = 1 << 3;
 
     @Position
     private final int mPosition;
@@ -77,6 +77,8 @@
 
     private final int mWeight;
 
+    private final boolean mSnapToGuide;
+
     // Do not allow specifying opposite positions
     private static final int[] INVALID_POSITIONS =
             { POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
@@ -104,6 +106,27 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight) {
+        this(width, height, position, direction, weight, false);
+    }
+
+    /**
+     * Constructs a {@link ComplicationLayoutParams}.
+     * @param width The width {@link android.view.View.MeasureSpec} for the view.
+     * @param height The height {@link android.view.View.MeasureSpec} for the view.
+     * @param position The place within the parent container where the view should be positioned.
+     * @param direction The direction the view should be laid out from either the parent container
+     *                  or preceding view.
+     * @param weight The weight that should be considered for this view when compared to other
+     *               views. This has an impact on the placement of the view but not the rendering of
+     *               the view.
+     * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
+     *                    will be automatically set to align with a predetermined guide for that
+     *                    side. For example, if the complication is aligned to the top end and
+     *                    direction is down, then the width of the complication will be set to span
+     *                    from the end of the parent to the guide.
+     */
+    public ComplicationLayoutParams(int width, int height, @Position int position,
+            @Direction int direction, int weight, boolean snapToGuide) {
         super(width, height);
 
         if (!validatePosition(position)) {
@@ -118,6 +141,8 @@
         mDirection = direction;
 
         mWeight = weight;
+
+        mSnapToGuide = snapToGuide;
     }
 
     /**
@@ -128,6 +153,7 @@
         mPosition = source.mPosition;
         mDirection = source.mDirection;
         mWeight = source.mWeight;
+        mSnapToGuide = source.mSnapToGuide;
     }
 
     private static boolean validateDirection(@Position int position, @Direction int direction) {
@@ -180,7 +206,19 @@
         return mPosition;
     }
 
+    /**
+     * Returns the set weight for the complication. The weight determines ordering a complication
+     * given the same position/direction.
+     */
     public int getWeight() {
         return mWeight;
     }
+
+    /**
+     * Returns whether the complication's dimension perpendicular to direction should be
+     * automatically set.
+     */
+    public boolean snapsToGuide() {
+        return mSnapToGuide;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
new file mode 100644
index 0000000..3a2a6ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
+
+import com.android.settingslib.dream.DreamBackend;
+
+/**
+ * A collection of utility methods for working with {@link Complication}.
+ */
+public class ComplicationUtils {
+    /**
+     * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to
+     * {@link ComplicationType}.
+     */
+    @Complication.ComplicationType
+    public static int convertComplicationType(@DreamBackend.ComplicationType int type) {
+        switch (type) {
+            case DreamBackend.COMPLICATION_TYPE_TIME:
+                return COMPLICATION_TYPE_TIME;
+            case DreamBackend.COMPLICATION_TYPE_DATE:
+                return COMPLICATION_TYPE_DATE;
+            case DreamBackend.COMPLICATION_TYPE_WEATHER:
+                return COMPLICATION_TYPE_WEATHER;
+            case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY:
+                return COMPLICATION_TYPE_AIR_QUALITY;
+            case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
+                return COMPLICATION_TYPE_CAST_INFO;
+            default:
+                return COMPLICATION_TYPE_NONE;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
new file mode 100644
index 0000000..59c52b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_VIEW;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Clock Date Complication that produce Clock Date view holder.
+ */
+public class DreamClockDateComplication implements Complication {
+    DreamClockDateComplicationComponent.Factory mComponentFactory;
+
+    /**
+     * Default constructor for {@link DreamClockDateComplication}.
+     */
+    @Inject
+    public DreamClockDateComplication(
+            DreamClockDateComplicationComponent.Factory componentFactory) {
+        mComponentFactory = componentFactory;
+    }
+
+    /**
+     * Create {@link DreamClockDateViewHolder}.
+     */
+    @Override
+    public ViewHolder createView(ComplicationViewModel model) {
+        return mComponentFactory.create().getViewHolder();
+    }
+
+    /**
+     * {@link CoreStartable} responsbile for registering {@link DreamClockDateComplication} with
+     * SystemUI.
+     */
+    public static class Registrant extends CoreStartable {
+        private final DreamOverlayStateController mDreamOverlayStateController;
+        private final DreamClockDateComplication mComplication;
+
+        /**
+         * Default constructor to register {@link DreamClockDateComplication}.
+         */
+        @Inject
+        public Registrant(Context context,
+                DreamOverlayStateController dreamOverlayStateController,
+                DreamClockDateComplication dreamClockDateComplication) {
+            super(context);
+            mDreamOverlayStateController = dreamOverlayStateController;
+            mComplication = dreamClockDateComplication;
+        }
+
+        @Override
+        public void start() {
+            mDreamOverlayStateController.addComplication(mComplication);
+        }
+    }
+
+    /**
+     * ViewHolder to contain value/logic associated with a Clock Date Complication View.
+     */
+    public static class DreamClockDateViewHolder implements ViewHolder {
+        private final View mView;
+        private final ComplicationLayoutParams mLayoutParams;
+
+        @Inject
+        DreamClockDateViewHolder(@Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) View view,
+                @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
+                        ComplicationLayoutParams layoutParams) {
+            mView = view;
+            mLayoutParams = layoutParams;
+        }
+
+        @Override
+        public View getView() {
+            return mView;
+        }
+
+        @Override
+        public ComplicationLayoutParams getLayoutParams() {
+            return mLayoutParams;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
new file mode 100644
index 0000000..b0c3a42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Clock Time Complication that produce Clock Time view holder.
+ */
+public class DreamClockTimeComplication implements Complication {
+    DreamClockTimeComplicationComponent.Factory mComponentFactory;
+
+    /**
+     * Default constructor for {@link DreamClockTimeComplication}.
+     */
+    @Inject
+    public DreamClockTimeComplication(
+            DreamClockTimeComplicationComponent.Factory componentFactory) {
+        mComponentFactory = componentFactory;
+    }
+
+    /**
+     * Create {@link DreamClockTimeViewHolder}.
+     */
+    @Override
+    public ViewHolder createView(ComplicationViewModel model) {
+        return mComponentFactory.create().getViewHolder();
+    }
+
+    /**
+     * {@link CoreStartable} responsbile for registering {@link DreamClockTimeComplication} with
+     * SystemUI.
+     */
+    public static class Registrant extends CoreStartable {
+        private final DreamOverlayStateController mDreamOverlayStateController;
+        private final DreamClockTimeComplication mComplication;
+
+        /**
+         * Default constructor to register {@link DreamClockTimeComplication}.
+         */
+        @Inject
+        public Registrant(Context context,
+                DreamOverlayStateController dreamOverlayStateController,
+                DreamClockTimeComplication dreamClockTimeComplication) {
+            super(context);
+            mDreamOverlayStateController = dreamOverlayStateController;
+            mComplication = dreamClockTimeComplication;
+        }
+
+        @Override
+        public void start() {
+            mDreamOverlayStateController.addComplication(mComplication);
+        }
+    }
+
+    /**
+     * ViewHolder to contain value/logic associated with a Clock Time Complication View.
+     */
+    public static class DreamClockTimeViewHolder implements ViewHolder {
+        private final View mView;
+        private final ComplicationLayoutParams mLayoutParams;
+
+        @Inject
+        DreamClockTimeViewHolder(@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+                @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+                        ComplicationLayoutParams layoutParams) {
+            mView = view;
+            mLayoutParams = layoutParams;
+        }
+
+        @Override
+        public View getView() {
+            return mView;
+        }
+
+        @Override
+        public ComplicationLayoutParams getLayoutParams() {
+            return mLayoutParams;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
new file mode 100644
index 0000000..cbdbef3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.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.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_VIEW;
+
+import android.app.smartspace.SmartspaceAction;
+import android.app.smartspace.SmartspaceTarget;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.widget.TextView;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.R;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent;
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Weather Complication that produce Weather view holder.
+ */
+public class DreamWeatherComplication implements Complication {
+    DreamWeatherComplicationComponent.Factory mComponentFactory;
+
+    /**
+     * Default constructor for {@link DreamWeatherComplication}.
+     */
+    @Inject
+    public DreamWeatherComplication(
+            DreamWeatherComplicationComponent.Factory componentFactory) {
+        mComponentFactory = componentFactory;
+    }
+
+    /**
+     * Create {@link DreamWeatherViewHolder}.
+     */
+    @Override
+    public ViewHolder createView(ComplicationViewModel model) {
+        return mComponentFactory.create().getViewHolder();
+    }
+
+    /**
+     * {@link CoreStartable} for registering {@link DreamWeatherComplication} with SystemUI.
+     */
+    public static class Registrant extends CoreStartable {
+        private final LockscreenSmartspaceController mSmartSpaceController;
+        private final DreamOverlayStateController mDreamOverlayStateController;
+        private final DreamWeatherComplication mComplication;
+
+        /**
+         * Default constructor to register {@link DreamWeatherComplication}.
+         */
+        @Inject
+        public Registrant(Context context,
+                LockscreenSmartspaceController smartspaceController,
+                DreamOverlayStateController dreamOverlayStateController,
+                DreamWeatherComplication dreamWeatherComplication) {
+            super(context);
+            mSmartSpaceController = smartspaceController;
+            mDreamOverlayStateController = dreamOverlayStateController;
+            mComplication = dreamWeatherComplication;
+        }
+
+        @Override
+        public void start() {
+            if (mSmartSpaceController.isEnabled()) {
+                mDreamOverlayStateController.addComplication(mComplication);
+            }
+        }
+    }
+
+    /**
+     * ViewHolder to contain value/logic associated with a Weather Complication View.
+     */
+    public static class DreamWeatherViewHolder implements ViewHolder {
+        private final TextView mView;
+        private final ComplicationLayoutParams mLayoutParams;
+        private final DreamWeatherViewController mViewController;
+
+        @Inject
+        DreamWeatherViewHolder(
+                @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
+                DreamWeatherViewController controller,
+                @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
+                        ComplicationLayoutParams layoutParams) {
+            mView = view;
+            mLayoutParams = layoutParams;
+            mViewController = controller;
+            mViewController.init();
+        }
+
+        @Override
+        public TextView getView() {
+            return mView;
+        }
+
+        @Override
+        public ComplicationLayoutParams getLayoutParams() {
+            return mLayoutParams;
+        }
+    }
+
+    /**
+     * ViewController to contain value/logic associated with a Weather Complication View.
+     */
+    static class DreamWeatherViewController extends ViewController<TextView> {
+        private final LockscreenSmartspaceController mSmartSpaceController;
+        private SmartspaceTargetListener mSmartspaceTargetListener;
+
+        @Inject
+        DreamWeatherViewController(
+                @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
+                LockscreenSmartspaceController smartspaceController
+        ) {
+            super(view);
+            mSmartSpaceController = smartspaceController;
+        }
+
+        @Override
+        protected void onViewAttached() {
+            mSmartspaceTargetListener = targets -> targets.forEach(
+                    t -> {
+                        if (t instanceof SmartspaceTarget
+                                && ((SmartspaceTarget) t).getFeatureType()
+                                == SmartspaceTarget.FEATURE_WEATHER) {
+                            final SmartspaceTarget target = (SmartspaceTarget) t;
+                            final SmartspaceAction headerAction = target.getHeaderAction();
+                            if (headerAction == null || TextUtils.isEmpty(
+                                    headerAction.getTitle())) {
+                                return;
+                            }
+
+                            String temperature = headerAction.getTitle().toString();
+                            mView.setText(temperature);
+                            final Icon icon = headerAction.getIcon();
+                            if (icon != null) {
+                                final int iconSize =
+                                        getResources().getDimensionPixelSize(
+                                                R.dimen.smart_action_button_icon_size);
+                                final Drawable iconDrawable = icon.loadDrawable(getContext());
+                                iconDrawable.setBounds(0, 0, iconSize, iconSize);
+                                mView.setCompoundDrawables(iconDrawable, null, null, null);
+                                mView.setCompoundDrawablePadding(
+                                        getResources().getDimensionPixelSize(
+                                                R.dimen.smart_action_button_icon_padding));
+
+                            }
+                        }
+                    });
+            mSmartSpaceController.addListener(mSmartspaceTargetListener);
+        }
+
+        @Override
+        protected void onViewDetached() {
+            mSmartSpaceController.removeListener(mSmartspaceTargetListener);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java
new file mode 100644
index 0000000..eaffb1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamClockDateComplication.DreamClockDateViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamClockDateComplicationComponent} is responsible for generating dependencies
+ * surrounding the
+ * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+        DreamClockDateComplicationComponent.DreamClockDateComplicationModule.class,
+})
+@DreamClockDateComplicationComponent.DreamClockDateComplicationScope
+public interface DreamClockDateComplicationComponent {
+    /**
+     * Creates {@link DreamClockDateViewHolder}.
+     */
+    DreamClockDateViewHolder getViewHolder();
+
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface DreamClockDateComplicationScope {
+    }
+
+    /**
+     * Generates {@link DreamClockDateComplicationComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        DreamClockDateComplicationComponent create();
+    }
+
+    /**
+     * Scoped values for {@link DreamClockDateComplicationComponent}.
+     */
+    @Module
+    interface DreamClockDateComplicationModule {
+        String DREAM_CLOCK_DATE_COMPLICATION_VIEW = "clock_date_complication_view";
+        String DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS =
+                "clock_date_complication_layout_params";
+        // Order weight of insert into parent container
+        int INSERT_ORDER_WEIGHT = 2;
+
+        /**
+         * Provides the complication view.
+         */
+        @Provides
+        @DreamClockDateComplicationScope
+        @Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW)
+        static View provideComplicationView(LayoutInflater layoutInflater) {
+            return Preconditions.checkNotNull(
+                    layoutInflater.inflate(R.layout.dream_overlay_complication_clock_date,
+                            null, false),
+                    "R.layout.dream_overlay_complication_clock_date did not properly inflated");
+        }
+
+        /**
+         * Provides the layout parameters for the complication view.
+         */
+        @Provides
+        @DreamClockDateComplicationScope
+        @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
+        static ComplicationLayoutParams provideLayoutParams() {
+            return new ComplicationLayoutParams(0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ComplicationLayoutParams.POSITION_BOTTOM
+                            | ComplicationLayoutParams.POSITION_START,
+                    ComplicationLayoutParams.DIRECTION_END,
+                    INSERT_ORDER_WEIGHT,
+                    true);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java
new file mode 100644
index 0000000..053c5b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamClockTimeComplication.DreamClockTimeViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamClockTimeComplicationComponent} is responsible for generating dependencies
+ * surrounding the
+ * Clock Time {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+        DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.class,
+})
+@DreamClockTimeComplicationComponent.DreamClockTimeComplicationScope
+public interface DreamClockTimeComplicationComponent {
+    /**
+     * Creates {@link DreamClockTimeViewHolder}.
+     */
+    DreamClockTimeViewHolder getViewHolder();
+
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface DreamClockTimeComplicationScope {
+    }
+
+    /**
+     * Generates {@link DreamClockTimeComplicationComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        DreamClockTimeComplicationComponent create();
+    }
+
+    /**
+     * Scoped values for {@link DreamClockTimeComplicationComponent}.
+     */
+    @Module
+    interface DreamClockTimeComplicationModule {
+        String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
+        String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS =
+                "clock_time_complication_layout_params";
+        // Order weight of insert into parent container
+        int INSERT_ORDER_WEIGHT = 0;
+
+        /**
+         * Provides the complication view.
+         */
+        @Provides
+        @DreamClockTimeComplicationScope
+        @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
+        static View provideComplicationView(LayoutInflater layoutInflater) {
+            return Preconditions.checkNotNull(
+                    layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time,
+                            null, false),
+                    "R.layout.dream_overlay_complication_clock_time did not properly inflated");
+        }
+
+        /**
+         * Provides the layout parameters for the complication view.
+         */
+        @Provides
+        @DreamClockTimeComplicationScope
+        @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+        static ComplicationLayoutParams provideLayoutParams() {
+            return new ComplicationLayoutParams(0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ComplicationLayoutParams.POSITION_BOTTOM
+                            | ComplicationLayoutParams.POSITION_START,
+                    ComplicationLayoutParams.DIRECTION_UP,
+                    INSERT_ORDER_WEIGHT,
+                    true);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
new file mode 100644
index 0000000..a282594
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamWeatherComplication.DreamWeatherViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamWeatherComplicationComponent} is responsible for generating dependencies surrounding
+ * the
+ * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+        DreamWeatherComplicationComponent.DreamWeatherComplicationModule.class,
+})
+@DreamWeatherComplicationComponent.DreamWeatherComplicationScope
+public interface DreamWeatherComplicationComponent {
+    /**
+     * Creates {@link DreamWeatherViewHolder}.
+     */
+    DreamWeatherViewHolder getViewHolder();
+
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface DreamWeatherComplicationScope {
+    }
+
+    /**
+     * Generates {@link DreamWeatherComplicationComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        DreamWeatherComplicationComponent create();
+    }
+
+    /**
+     * Scoped values for {@link DreamWeatherComplicationComponent}.
+     */
+    @Module
+    interface DreamWeatherComplicationModule {
+        String DREAM_WEATHER_COMPLICATION_VIEW = "weather_complication_view";
+        String DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS =
+                "weather_complication_layout_params";
+        // Order weight of insert into parent container
+        int INSERT_ORDER_WEIGHT = 1;
+
+        /**
+         * Provides the complication view.
+         */
+        @Provides
+        @DreamWeatherComplicationScope
+        @Named(DREAM_WEATHER_COMPLICATION_VIEW)
+        static TextView provideComplicationView(LayoutInflater layoutInflater) {
+            return Preconditions.checkNotNull((TextView)
+                            layoutInflater.inflate(R.layout.dream_overlay_complication_weather,
+                                    null, false),
+                    "R.layout.dream_overlay_complication_weather did not properly inflated");
+        }
+
+        /**
+         * Provides the layout parameters for the complication view.
+         */
+        @Provides
+        @DreamWeatherComplicationScope
+        @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
+        static ComplicationLayoutParams provideLayoutParams() {
+            return new ComplicationLayoutParams(0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ComplicationLayoutParams.POSITION_BOTTOM
+                            | ComplicationLayoutParams.POSITION_START,
+                    ComplicationLayoutParams.DIRECTION_END,
+                    INSERT_ORDER_WEIGHT,
+                    true);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
new file mode 100644
index 0000000..8e4fb37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication.dagger;
+
+import com.android.systemui.dagger.SystemUIBinder;
+
+import dagger.Module;
+
+/**
+ * Module for all components with corresponding dream layer complications registered in
+ * {@link SystemUIBinder}.
+ */
+@Module(subcomponents = {
+        DreamClockTimeComplicationComponent.class,
+        DreamClockDateComplicationComponent.class,
+        DreamWeatherComplicationComponent.class,
+})
+public interface RegisteredComplicationsModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 3d2f924..d8af9e5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dreams.dagger;
 
+import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
 import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
 
 import dagger.Module;
@@ -25,6 +26,7 @@
  */
 @Module(includes = {
             DreamTouchModule.class,
+            RegisteredComplicationsModule.class,
         },
         subcomponents = {
             DreamOverlayComponent.class,
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 503817a..4eb5cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -34,7 +34,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
 import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -44,7 +43,6 @@
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.IntoSet;
 
 /** Dagger module for {@link DreamOverlayComponent}. */
 @Module
@@ -149,12 +147,4 @@
     static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
         return lifecycleOwner.getLifecycle();
     }
-
-    // TODO: This stub should be removed once there is a {@link DreamTouchHandler}
-    // implementation present.
-    @Provides
-    @IntoSet
-    static DreamTouchHandler provideDreamTouchHandler() {
-        return session -> { };
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
new file mode 100644
index 0000000..d16c8c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.touch;
+
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
+
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
+ */
+public class BouncerSwipeTouchHandler implements DreamTouchHandler {
+    /**
+     * An interface for creating ValueAnimators.
+     */
+    public interface ValueAnimatorCreator {
+        /**
+         * Creates {@link ValueAnimator}.
+         */
+        ValueAnimator create(float start, float finish);
+    }
+
+    /**
+     * An interface for obtaining VelocityTrackers.
+     */
+    public interface VelocityTrackerFactory {
+        /**
+         * Obtains {@link VelocityTracker}.
+         */
+        VelocityTracker obtain();
+    }
+
+    public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
+
+    private static final String TAG = "BouncerSwipeTouchHandler";
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final float mBouncerZoneScreenPercentage;
+
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private float mCurrentExpansion;
+    private final StatusBar mStatusBar;
+
+    private VelocityTracker mVelocityTracker;
+
+    private final FlingAnimationUtils mFlingAnimationUtils;
+    private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+    private Boolean mCapture;
+
+    private TouchSession mTouchSession;
+
+    private ValueAnimatorCreator mValueAnimatorCreator;
+
+    private VelocityTrackerFactory mVelocityTrackerFactory;
+
+    private final GestureDetector.OnGestureListener mOnGestureListener =
+            new  GestureDetector.SimpleOnGestureListener() {
+                boolean mTrack;
+                boolean mBouncerPresent;
+
+                @Override
+                public boolean onDown(MotionEvent e) {
+                    // We only consider gestures that originate from the lower portion of the
+                    // screen.
+                    final float displayHeight = mStatusBar.getDisplayHeight();
+
+                    mBouncerPresent = mStatusBar.isBouncerShowing();
+
+                    // The target zone is either at the top or bottom of the screen, dependent on
+                    // whether the bouncer is present.
+                    final float zonePercentage =
+                            Math.abs(e.getY() - (mBouncerPresent ? 0 : displayHeight))
+                                    / displayHeight;
+
+                    mTrack =  zonePercentage < mBouncerZoneScreenPercentage;
+
+                    // Never capture onDown. While this might lead to some false positive touches
+                    // being sent to other windows/layers, this is necessary to make sure the
+                    // proper touch event sequence is received by others in the event we do not
+                    // consume the sequence here.
+                    return false;
+                }
+
+                @Override
+                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+                        float distanceY) {
+                    // Do not handle scroll gestures if not tracking touch events.
+                    if (!mTrack) {
+                        return false;
+                    }
+
+                    if (mCapture == null) {
+                        // If the user scrolling favors a vertical direction, begin capturing
+                        // scrolls.
+                        mCapture = Math.abs(distanceY) > Math.abs(distanceX);
+
+                        if (mCapture) {
+                            // Since the user is dragging the bouncer up, set scrimmed to false.
+                            mStatusBarKeyguardViewManager.showBouncer(false);
+                        }
+                    }
+
+                    if (!mCapture) {
+                        return false;
+                    }
+
+                    // For consistency, we adopt the expansion definition found in the
+                    // PanelViewController. In this case, expansion refers to the view above the
+                    // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+                    // is fully hidden at full expansion (1) and fully visible when fully collapsed
+                    // (0).
+                    final float screenTravelPercentage =
+                            Math.abs((e1.getY() - e2.getY()) / mStatusBar.getDisplayHeight());
+                    setPanelExpansion(
+                            mBouncerPresent ? screenTravelPercentage : 1 - screenTravelPercentage);
+
+                    return true;
+                }
+            };
+
+    private void setPanelExpansion(float expansion) {
+        mCurrentExpansion = expansion;
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(mCurrentExpansion, false, true);
+    }
+
+    @Inject
+    public BouncerSwipeTouchHandler(
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            StatusBar statusBar,
+            NotificationShadeWindowController notificationShadeWindowController,
+            ValueAnimatorCreator valueAnimatorCreator,
+            VelocityTrackerFactory velocityTrackerFactory,
+            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+                    FlingAnimationUtils flingAnimationUtils,
+            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+                    FlingAnimationUtils flingAnimationUtilsClosing,
+            @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) {
+        mStatusBar = statusBar;
+        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mNotificationShadeWindowController = notificationShadeWindowController;
+        mBouncerZoneScreenPercentage = swipeRegionPercentage;
+        mFlingAnimationUtils = flingAnimationUtils;
+        mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
+        mValueAnimatorCreator = valueAnimatorCreator;
+        mVelocityTrackerFactory = velocityTrackerFactory;
+    }
+
+    @Override
+    public void onSessionStart(TouchSession session) {
+        mVelocityTracker = mVelocityTrackerFactory.obtain();
+        mTouchSession = session;
+        mVelocityTracker.clear();
+        mNotificationShadeWindowController.setForcePluginOpen(true, this);
+        session.registerGestureListener(mOnGestureListener);
+        session.registerInputListener(ev -> onMotionEvent(ev));
+
+    }
+
+    @Override
+    public void onSessionEnd(TouchSession session) {
+        mVelocityTracker.recycle();
+        mCapture = null;
+        mNotificationShadeWindowController.setForcePluginOpen(false, this);
+    }
+
+    private void onMotionEvent(InputEvent event) {
+        if (!(event instanceof MotionEvent)) {
+            Log.e(TAG, "non MotionEvent received:" + event);
+            return;
+        }
+
+        final MotionEvent motionEvent = (MotionEvent) event;
+
+        switch(motionEvent.getAction()) {
+            case MotionEvent.ACTION_UP:
+                // If we are not capturing any input, there is no need to consider animating to
+                // finish transition.
+                if (mCapture == null || !mCapture) {
+                    break;
+                }
+
+                // We must capture the resulting velocities as resetMonitor() will clear these
+                // values.
+                mVelocityTracker.computeCurrentVelocity(1000);
+                final float verticalVelocity = mVelocityTracker.getYVelocity();
+                final float horizontalVelocity = mVelocityTracker.getXVelocity();
+
+                final float velocityVector =
+                        (float) Math.hypot(horizontalVelocity, verticalVelocity);
+
+
+                final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector)
+                            ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE;
+                flingToExpansion(verticalVelocity, expansion);
+
+                if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
+                    mStatusBarKeyguardViewManager.reset(false);
+                }
+                mTouchSession.pop();
+                break;
+            default:
+                mVelocityTracker.addMovement(motionEvent);
+                break;
+        }
+    }
+
+    private ValueAnimator createExpansionAnimator(float targetExpansion) {
+        final ValueAnimator animator =
+                mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
+        animator.addUpdateListener(
+                animation -> {
+                    setPanelExpansion((float) animation.getAnimatedValue());
+                });
+        return animator;
+    }
+
+    protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
+        // Fully expand if the user has expanded the bouncer less than halfway or final velocity was
+        // positive, indicating an downward direction.
+        if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+            return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
+        } else {
+            return velocity > 0;
+        }
+    }
+
+    protected void flingToExpansion(float velocity, float expansion) {
+        final float viewHeight = mStatusBar.getDisplayHeight();
+        final float currentHeight = viewHeight * mCurrentExpansion;
+        final float targetHeight = viewHeight * expansion;
+
+        final ValueAnimator animator = createExpansionAnimator(expansion);
+        if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
+            // The animation utils deal in pixel units, rather than expansion height.
+            mFlingAnimationUtils.apply(animator, currentHeight, targetHeight, velocity, viewHeight);
+        } else {
+            mFlingAnimationUtilsClosing.apply(
+                    animator, mCurrentExpansion, currentHeight, targetHeight, viewHeight);
+        }
+
+        animator.start();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
new file mode 100644
index 0000000..b9436f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -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.dreams.touch.dagger;
+
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.VelocityTracker;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.statusbar.phone.PanelViewController;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Named;
+import javax.inject.Provider;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+
+/**
+ * This module captures the components associated with {@link BouncerSwipeTouchHandler}.
+ */
+@Module
+public class BouncerSwipeModule {
+    /**
+     * The region, defined as the percentage of the screen, from which a touch gesture to start
+     * swiping up to the bouncer can occur.
+     */
+    public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region";
+
+    /**
+     * The {@link android.view.animation.AnimationUtils} for animating the bouncer closing.
+     */
+    public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING =
+            "swipe_to_bouncer_fling_animation_utils_closing";
+
+    /**
+     * The {@link android.view.animation.AnimationUtils} for animating the bouncer opening.
+     */
+    public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING =
+                    "swipe_to_bouncer_fling_animation_utils_opening";
+
+    /**
+     * Provides {@link BouncerSwipeTouchHandler} for inclusion in touch handling over the dream.
+     */
+    @Provides
+    @IntoSet
+    public static DreamTouchHandler providesBouncerSwipeTouchHandler(
+            BouncerSwipeTouchHandler touchHandler) {
+        return touchHandler;
+    }
+
+    /**
+     * Provides {@link android.view.animation.AnimationUtils} for closing.
+     */
+    @Provides
+    @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+    public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsClosing(
+            Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
+        return flingAnimationUtilsBuilderProvider.get()
+                .reset()
+                .setMaxLengthSeconds(PanelViewController.FLING_CLOSING_MAX_LENGTH_SECONDS)
+                .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+                .build();
+    }
+
+    /**
+     * Provides {@link android.view.animation.AnimationUtils} for opening.
+     */
+    @Provides
+    @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+    public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsOpening(
+            Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
+        return flingAnimationUtilsBuilderProvider.get()
+                .reset()
+                .setMaxLengthSeconds(PanelViewController.FLING_MAX_LENGTH_SECONDS)
+                .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+                .build();
+    }
+
+    /**
+     * Provides the region to start swipe gestures from.
+     */
+    @Provides
+    @Named(SWIPE_TO_BOUNCER_START_REGION)
+    public static float providesSwipeToBouncerStartRegion(@Main Resources resources) {
+        TypedValue typedValue = new TypedValue();
+        resources.getValue(R.dimen.dream_overlay_bouncer_start_region_screen_percentage,
+                typedValue, true);
+        return typedValue.getFloat();
+    }
+
+    /**
+     * Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply
+     * a wrapper around {@link ValueAnimator}.
+     */
+    @Provides
+    public static BouncerSwipeTouchHandler.ValueAnimatorCreator providesValueAnimatorCreator() {
+        return (start, finish) -> ValueAnimator.ofFloat(start, finish);
+    }
+
+    /**
+     * Provides the default {@link BouncerSwipeTouchHandler.VelocityTrackerFactory}. which is a
+     * passthrough to {@link android.view.VelocityTracker}.
+     */
+    @Provides
+    public static BouncerSwipeTouchHandler.VelocityTrackerFactory providesVelocityTrackerFactory() {
+        return () -> VelocityTracker.obtain();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
index 7b77b59..dad0004 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
@@ -21,8 +21,10 @@
 /**
  * {@link DreamTouchModule} encapsulates dream touch-related components.
  */
-@Module(subcomponents = {
-        InputSessionComponent.class,
+@Module(includes = {
+            BouncerSwipeModule.class,
+        }, subcomponents = {
+            InputSessionComponent.class,
 })
 public interface DreamTouchModule {
     String INPUT_SESSION_NAME = "INPUT_SESSION_NAME";
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 97edf3b..357a68f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -107,11 +107,16 @@
     public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
             new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
 
+    public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, false);
+
     /***************************************/
     // 600- status bar
     public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS =
             new BooleanFlag(601, false);
 
+    public static final BooleanFlag STATUS_BAR_USER_SWITCHER =
+            new BooleanFlag(602, false);
+
     /***************************************/
     // 700 - dialer/calls
     public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
index d1a103e..de67ba8 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
@@ -40,6 +40,7 @@
     private boolean mIsDropDownMode;
     private int mMenuVerticalPadding = 0;
     private int mGlobalActionsSidePadding = 0;
+    private int mMaximumWidthThresholdDp = 800;
     private ListAdapter mAdapter;
     private AdapterView.OnItemLongClickListener mOnItemLongClickListener;
 
@@ -92,6 +93,8 @@
 
             // width should be between [.5, .9] of screen
             int parentWidth = res.getSystem().getDisplayMetrics().widthPixels;
+            float parentDensity = res.getSystem().getDisplayMetrics().density;
+            float parentWidthDp = parentWidth / parentDensity;
             int widthSpec = MeasureSpec.makeMeasureSpec(
                     (int) (parentWidth * 0.9), MeasureSpec.AT_MOST);
             int maxWidth = 0;
@@ -101,9 +104,12 @@
                 int w = child.getMeasuredWidth();
                 maxWidth = Math.max(w, maxWidth);
             }
-            int width = Math.max(maxWidth, (int) (parentWidth * 0.5));
-            listView.setPadding(0, mMenuVerticalPadding, 0, mMenuVerticalPadding);
 
+            int width = maxWidth;
+            if (parentWidthDp < mMaximumWidthThresholdDp) {
+                width = Math.max(maxWidth, (int) (parentWidth * 0.5));
+            }
+            listView.setPadding(0, mMenuVerticalPadding, 0, mMenuVerticalPadding);
             setWidth(width);
             if (getAnchorView().getLayoutDirection() == LayoutDirection.LTR) {
                 setHorizontalOffset(getAnchorView().getWidth() - mGlobalActionsSidePadding - width);
diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java
new file mode 100644
index 0000000..b304c3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.os.Bundle;
+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.tv.TvBottomSheetActivity;
+
+import javax.inject.Inject;
+
+/**
+ * Confirmation dialog shown when Set Menu Language CEC message was received.
+ */
+public class HdmiCecSetMenuLanguageActivity extends TvBottomSheetActivity
+        implements View.OnClickListener {
+    private static final String TAG = HdmiCecSetMenuLanguageActivity.class.getSimpleName();
+
+    private final HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
+
+    @Inject
+    public HdmiCecSetMenuLanguageActivity(
+            HdmiCecSetMenuLanguageHelper hdmiCecSetMenuLanguageHelper) {
+        mHdmiCecSetMenuLanguageHelper = hdmiCecSetMenuLanguageHelper;
+    }
+
+    @Override
+    public final void onCreate(Bundle b) {
+        super.onCreate(b);
+        getWindow().addPrivateFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        String languageTag = getIntent().getStringExtra(HdmiControlManager.EXTRA_LOCALE);
+        mHdmiCecSetMenuLanguageHelper.setLocale(languageTag);
+        if (mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()) {
+            finish();
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        CharSequence title = getString(R.string.hdmi_cec_set_menu_language_title,
+                mHdmiCecSetMenuLanguageHelper.getLocale().getDisplayLanguage());
+        CharSequence text = getString(R.string.hdmi_cec_set_menu_language_description);
+        initUI(title, text);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getId() == R.id.bottom_sheet_positive_button) {
+            mHdmiCecSetMenuLanguageHelper.acceptLocale();
+        } else {
+            mHdmiCecSetMenuLanguageHelper.declineLocale();
+        }
+        finish();
+    }
+
+    void initUI(CharSequence title, CharSequence text) {
+        TextView titleTextView = findViewById(R.id.bottom_sheet_title);
+        TextView contentTextView = findViewById(R.id.bottom_sheet_body);
+        ImageView icon = findViewById(R.id.bottom_sheet_icon);
+        ImageView secondIcon = findViewById(R.id.bottom_sheet_second_icon);
+        Button okButton = findViewById(R.id.bottom_sheet_positive_button);
+        Button cancelButton = findViewById(R.id.bottom_sheet_negative_button);
+
+        titleTextView.setText(title);
+        contentTextView.setText(text);
+        icon.setImageResource(com.android.internal.R.drawable.ic_settings_language);
+        secondIcon.setVisibility(View.GONE);
+
+        okButton.setText(R.string.hdmi_cec_set_menu_language_accept);
+        okButton.setOnClickListener(this);
+
+        cancelButton.setText(R.string.hdmi_cec_set_menu_language_decline);
+        cancelButton.setOnClickListener(this);
+        cancelButton.requestFocus();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java
new file mode 100644
index 0000000..1f58112
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.hdmi;
+
+import android.provider.Settings;
+
+import com.android.internal.app.LocalePicker;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Helper class to separate model and view for system language change initiated by HDMI CEC.
+ */
+@SysUISingleton
+public class HdmiCecSetMenuLanguageHelper {
+    private static final String TAG = HdmiCecSetMenuLanguageHelper.class.getSimpleName();
+    private static final String SEPARATOR = ",";
+
+    private final Executor mBackgroundExecutor;
+    private final SecureSettings mSecureSettings;
+
+    private Locale mLocale;
+    private HashSet<String> mDenylist;
+
+    @Inject
+    public HdmiCecSetMenuLanguageHelper(@Background Executor executor,
+            SecureSettings secureSettings) {
+        mBackgroundExecutor = executor;
+        mSecureSettings = secureSettings;
+        String denylist = mSecureSettings.getString(
+                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST);
+        mDenylist = new HashSet<>(denylist == null
+                ? Collections.EMPTY_SET
+                : Arrays.asList(denylist.split(SEPARATOR)));
+    }
+
+    /**
+     * Set internal locale based on given language tag.
+     */
+    public void setLocale(String languageTag) {
+        mLocale = Locale.forLanguageTag(languageTag);
+    }
+
+    /**
+     * Returns the locale from {@code <Set Menu Language>} CEC message.
+     */
+    public Locale getLocale() {
+        return mLocale;
+    }
+
+    /**
+     * Returns whether the locale from {@code <Set Menu Language>} CEC message was already
+     * denylisted.
+     */
+    public boolean isLocaleDenylisted() {
+        return mDenylist.contains(mLocale.toLanguageTag());
+    }
+
+    /**
+     * Accepts the new locale and updates system language.
+     */
+    public void acceptLocale() {
+        mBackgroundExecutor.execute(() -> LocalePicker.updateLocale(mLocale));
+    }
+
+    /**
+     * Declines the locale and puts it on the denylist.
+     */
+    public void declineLocale() {
+        mDenylist.add(mLocale.toLanguageTag());
+        mSecureSettings.putString(Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST,
+                String.join(SEPARATOR, mDenylist));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 701d139..fd2c6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -106,6 +106,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -138,7 +139,7 @@
  * state of the keyguard, power management events that effect whether the keyguard
  * should be shown or reset, callbacks to the phone window manager to notify
  * it of when the keyguard is showing, and events from the keyguard view itself
- * stating that the keyguard was succesfully unlocked.
+ * stating that the keyguard was successfully unlocked.
  *
  * Note that the keyguard view is shown when the screen is off (as appropriate)
  * so that once the screen comes on, it will be ready immediately.
@@ -152,15 +153,15 @@
  * - the keyguard is showing
  *
  * Example external events that translate to keyguard view changes:
- * - screen turned off -> reset the keyguard, and show it so it will be ready
+ * - screen turned off -> reset the keyguard, and show it, so it will be ready
  *   next time the screen turns on
  * - keyboard is slid open -> if the keyguard is not secure, hide it
  *
  * Events from the keyguard view:
- * - user succesfully unlocked keyguard -> hide keyguard view, and no longer
+ * - user successfully unlocked keyguard -> hide keyguard view, and no longer
  *   restrict input events.
  *
- * Note: in addition to normal power managment events that effect the state of
+ * Note: in addition to normal power management events that effect the state of
  * whether the keyguard should be showing, external apps and services may request
  * that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}.  When
  * false, this will override all other conditions for turning on the keyguard.
@@ -224,7 +225,7 @@
     /**
      * How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()}
      * callback before unblocking a call to {@link #setKeyguardEnabled(boolean)}
-     * that is reenabling the keyguard.
+     * that is re-enabling the keyguard.
      */
     private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
 
@@ -233,6 +234,7 @@
      * keyguard to show even if it is disabled for the current user.
      */
     public static final String OPTION_FORCE_SHOW = "force_show";
+    private final DreamOverlayStateController mDreamOverlayStateController;
 
     /** The stream type that the lock sounds are tied to. */
     private int mUiSoundsStreamType;
@@ -273,14 +275,14 @@
     // these are protected by synchronized (this)
 
     /**
-     * External apps (like the phone app) can tell us to disable the keygaurd.
+     * External apps (like the phone app) can tell us to disable the keyguard.
      */
     private boolean mExternallyEnabled = true;
 
     /**
      * Remember if an external call to {@link #setKeyguardEnabled} with value
      * false caused us to hide the keyguard, so that we need to reshow it once
-     * the keygaurd is reenabled with another call with value true.
+     * the keyguard is re-enabled with another call with value true.
      */
     private boolean mNeedToReshowWhenReenabled = false;
 
@@ -291,6 +293,9 @@
     // AOD is enabled and status bar is in AOD state.
     private boolean mAodShowing;
 
+    // Dream overlay is visible.
+    private boolean mDreamOverlayShowing;
+
     /** Cached value of #isInputRestricted */
     private boolean mInputRestricted;
 
@@ -304,7 +309,7 @@
     private int mDelayedShowingSequence;
 
     /**
-     * Simiar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+     * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
      */
     private int mDelayedProfileShowingSequence;
 
@@ -341,7 +346,7 @@
     private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
 
     /**
-     * Whether a hide is pending an we are just waiting for #startKeyguardExitAnimation to be
+     * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
      * called.
      * */
     private boolean mHiding;
@@ -355,7 +360,7 @@
                     | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
 
     /**
-     * {@link #setKeyguardEnabled} waits on this condition when it reenables
+     * {@link #setKeyguardEnabled} waits on this condition when it re-enables
      * the keyguard.
      */
     private boolean mWaitingUntilKeyguardVisible = false;
@@ -470,6 +475,14 @@
             }
     };
 
+    private final DreamOverlayStateController.Callback mDreamOverlayStateCallback =
+            new DreamOverlayStateController.Callback() {
+                @Override
+                public void onStateChanged() {
+                    mDreamOverlayShowing = mDreamOverlayStateController.isOverlayActive();
+                }
+            };
+
     KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
 
         @Override
@@ -494,7 +507,7 @@
             synchronized (KeyguardViewMediator.this) {
                 resetKeyguardDonePendingLocked();
                 if (mLockPatternUtils.isLockScreenDisabled(userId)) {
-                    // If we switching to a user that has keyguard disabled, dismiss keyguard.
+                    // If we are switching to a user that has keyguard disabled, dismiss keyguard.
                     dismiss(null /* callback */, null /* message */);
                 } else {
                     resetStateLocked();
@@ -508,7 +521,7 @@
             if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
             if (userId != UserHandle.USER_SYSTEM) {
                 UserInfo info = UserManager.get(mContext).getUserInfo(userId);
-                // Don't try to dismiss if the user has Pin/Patter/Password set
+                // Don't try to dismiss if the user has Pin/Pattern/Password set
                 if (info == null || mLockPatternUtils.isSecure(userId)) {
                     return;
                 } else if (info.isGuest() || info.isDemo()) {
@@ -836,6 +849,7 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
+            DreamOverlayStateController dreamOverlayStateController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy) {
         super(context);
         mFalsingCollector = falsingCollector;
@@ -875,6 +889,7 @@
         mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
         mScreenOffAnimationController = screenOffAnimationController;
         mInteractionJankMonitor = interactionJankMonitor;
+        mDreamOverlayStateController = dreamOverlayStateController;
     }
 
     public void userActivity() {
@@ -980,6 +995,7 @@
             mSystemReady = true;
             doKeyguardLocked(null);
             mUpdateMonitor.registerCallback(mUpdateCallback);
+            mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
         }
         // Most services aren't available until the system reaches the ready state, so we
         // send it here when the device first boots.
@@ -1041,7 +1057,7 @@
 
         mUpdateMonitor.dispatchStartedGoingToSleep(offReason);
 
-        // Reset keyguard going away state so we can start listening for fingerprint. We
+        // Reset keyguard going away state, so we can start listening for fingerprint. We
         // explicitly DO NOT want to call
         // mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
         // here, since that will mess with the device lock state.
@@ -1121,9 +1137,9 @@
     }
 
     private long getLockTimeout(int userId) {
-        // if the screen turned off because of timeout or the user hit the power button
+        // if the screen turned off because of timeout or the user hit the power button,
         // and we don't need to lock immediately, set an alarm
-        // to enable it a little bit later (i.e, give the user a chance
+        // to enable it a bit later (i.e, give the user a chance
         // to turn the screen back on within a certain window without
         // having to unlock the screen)
         final ContentResolver cr = mContext.getContentResolver();
@@ -1217,7 +1233,7 @@
     }
 
     /**
-     * Let's us know when the device is waking up.
+     * It will let us know when the device is waking up.
      */
     public void onStartedWakingUp(boolean cameraGestureTriggered) {
         Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
@@ -1299,7 +1315,7 @@
                 if (mExitSecureCallback != null) {
                     if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
                     // we're in the process of handling a request to verify the user
-                    // can get past the keyguard. ignore extraneous requests to disable / reenable
+                    // can get past the keyguard. ignore extraneous requests to disable / re-enable
                     return;
                 }
 
@@ -1310,7 +1326,7 @@
                 updateInputRestrictedLocked();
                 hideLocked();
             } else if (enabled && mNeedToReshowWhenReenabled) {
-                // reenabled after previously hidden, reshow
+                // re-enabled after previously hidden, reshow
                 if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
                         + "status bar expansion");
                 mNeedToReshowWhenReenabled = false;
@@ -1328,8 +1344,8 @@
                 } else {
                     showLocked(null);
 
-                    // block until we know the keygaurd is done drawing (and post a message
-                    // to unblock us after a timeout so we don't risk blocking too long
+                    // block until we know the keyguard is done drawing (and post a message
+                    // to unblock us after a timeout, so we don't risk blocking too long
                     // and causing an ANR).
                     mWaitingUntilKeyguardVisible = true;
                     mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
@@ -1917,7 +1933,7 @@
 
             mExitSecureCallback = null;
 
-            // after succesfully exiting securely, no need to reshow
+            // after successfully exiting securely, no need to reshow
             // the keyguard when they've released the lock
             mExternallyEnabled = true;
             mNeedToReshowWhenReenabled = false;
@@ -1992,7 +2008,7 @@
                 if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
 
                 int id = mLockSounds.play(soundId,
-                        mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+                        mLockSoundVolume, mLockSoundVolume, 1/*priority*/, 0/*loop*/, 1.0f/*rate*/);
                 synchronized (this) {
                     mLockSoundStreamId = id;
                 }
@@ -2078,7 +2094,7 @@
                     || mScreenOnCoordinator.getWakeAndUnlocking()
                             && mWallpaperSupportsAmbientMode) {
                 // When the wallpaper supports ambient mode, the scrim isn't fully opaque during
-                // wake and unlock and we should fade in the app on top of the wallpaper
+                // wake and unlock, and we should fade in the app on top of the wallpaper
                 flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
             }
             if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2123,7 +2139,7 @@
         Trace.beginSection("KeyguardViewMediator#handleHide");
 
         // It's possible that the device was unlocked in a dream state. It's time to wake up.
-        if (mAodShowing) {
+        if (mAodShowing || mDreamOverlayShowing) {
             PowerManager pm = mContext.getSystemService(PowerManager.class);
             pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                     "com.android.systemui:BOUNCER_DOZING");
@@ -2167,15 +2183,15 @@
         synchronized (KeyguardViewMediator.this) {
 
             // Tell ActivityManager that we canceled the keyguard animation if
-            // handleStartKeyguardExitAnimation was called but we're not hiding the keyguard, unless
-            // we're animating the surface behind the keyguard and will be hiding the keyguard
-            // shortly.
+            // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard,
+            // unless we're animating the surface behind the keyguard and will be hiding the
+            // keyguard shortly.
             if (!mHiding
                     && !mSurfaceBehindRemoteAnimationRequested
                     && !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
                 if (finishedCallback != null) {
                     // There will not execute animation, send a finish callback to ensure the remote
-                    // animation won't hanging there.
+                    // animation won't hang there.
                     try {
                         finishedCallback.onAnimationFinished();
                     } catch (RemoteException e) {
@@ -2192,7 +2208,7 @@
             if (mScreenOnCoordinator.getWakeAndUnlocking()) {
 
                 // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
-                // the next draw from here so we don't have to wait for window manager to signal
+                // the next draw from here, so we don't have to wait for window manager to signal
                 // this to our ViewRootImpl.
                 mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
                 mScreenOnCoordinator.setWakeAndUnlocking(false);
@@ -2344,7 +2360,7 @@
     }
 
     /**
-     * Called if the keyguard exit animation has been cancelled and we should dismiss to the
+     * Called if the keyguard exit animation has been cancelled, and we should dismiss to the
      * keyguard.
      *
      * This can happen due to the system cancelling the RemoteAnimation (due to a timeout, a new
@@ -2595,7 +2611,7 @@
     }
 
     /**
-     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
      * the wallpaper and keyguard flag, and WindowManager has started running keyguard exit
      * animation.
      *
@@ -2609,7 +2625,7 @@
     }
 
     /**
-     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
      * the wallpaper and keyguard flag, and System UI should start running keyguard exit animation.
      *
      * @param apps The list of apps to animate.
@@ -2625,7 +2641,7 @@
     }
 
     /**
-     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
      * the wallpaper and keyguard flag, and start running keyguard exit animation.
      *
      * @param startTime the start time of the animation in uptime milliseconds. Deprecated.
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 f14d130..b49b49cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -37,6 +37,7 @@
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -100,6 +101,7 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
+            DreamOverlayStateController dreamOverlayStateController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController) {
         return new KeyguardViewMediator(
                 context,
@@ -125,6 +127,7 @@
                 notificationShadeDepthController,
                 screenOnCoordinator,
                 interactionJankMonitor,
+                dreamOverlayStateController,
                 notificationShadeWindowController
         );
     }
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index b15807c..6d589aa 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -176,14 +176,9 @@
             buffer.removeFirst()
         }
         buffer.add(message as LogMessageImpl)
-        if (systrace) {
-            val messageStr = message.printer(message)
-            Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", "$name - $messageStr")
-        }
-        if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
-                logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
-            echo(message)
-        }
+        val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) ||
+                logcatEchoTracker.isTagLoggable(message.tag, message.level)
+        echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
     }
 
     /** Converts the entire buffer to a newline-delimited string */
@@ -232,8 +227,24 @@
         pw.println(message.printer(message))
     }
 
-    private fun echo(message: LogMessage) {
-        val strMessage = message.printer(message)
+    private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
+        if (toLogcat || toSystrace) {
+            val strMessage = message.printer(message)
+            if (toSystrace) {
+                echoToSystrace(message, strMessage)
+            }
+            if (toLogcat) {
+                echoToLogcat(message, strMessage)
+            }
+        }
+    }
+
+    private fun echoToSystrace(message: LogMessage, strMessage: String) {
+        Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events",
+            "$name - ${message.level.shortString} ${message.tag}: $strMessage")
+    }
+
+    private fun echoToLogcat(message: LogMessage, strMessage: String) {
         when (message.level) {
             LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
             LogLevel.DEBUG -> Log.d(message.tag, strMessage)
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 44727f2..48f4826 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -159,8 +159,7 @@
     }
 
     fun refreshMediaPosition() {
-        val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD ||
-                statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER)
+        val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD)
         // mediaHost.visible required for proper animations handling
         visible = mediaHost.visible &&
                 !bypassController.bypassEnabled &&
@@ -196,4 +195,4 @@
             visibilityChangedListener?.invoke(newVisibility == View.VISIBLE)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index c404f7a..f893f36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -411,7 +411,6 @@
 
     // Returns true if new player is added
     private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData): Boolean {
-        val dataCopy = data.copy(backgroundColor = bgColor)
         MediaPlayerData.moveIfExists(oldKey, key)
         val existingPlayer = MediaPlayerData.getMediaPlayer(key)
         val curVisibleMediaKey = MediaPlayerData.playerKeys()
@@ -431,14 +430,14 @@
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
             newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
-            newPlayer.bindPlayer(dataCopy, key)
+            newPlayer.bindPlayer(data, key)
             newPlayer.setListening(currentlyExpanded)
-            MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer, systemClock)
+            MediaPlayerData.addMediaPlayer(key, data, newPlayer, systemClock)
             updatePlayerToState(newPlayer, noAnimation = true)
             reorderAllPlayers(curVisibleMediaKey)
         } else {
-            existingPlayer.bindPlayer(dataCopy, key)
-            MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer, systemClock)
+            existingPlayer.bindPlayer(data, key)
+            MediaPlayerData.addMediaPlayer(key, data, existingPlayer, systemClock)
             if (visualStabilityManager.isReorderingAllowed || shouldScrollToActivePlayer) {
                 reorderAllPlayers(curVisibleMediaKey)
             } else {
@@ -543,7 +542,11 @@
     }
 
     private fun getForegroundColor(): Int {
-        return context.getColor(android.R.color.system_accent2_900)
+        return if (mediaFlags.useMediaSessionLayout()) {
+            context.getColor(android.R.color.system_neutral2_200)
+        } else {
+            context.getColor(android.R.color.system_accent2_900)
+        }
     }
 
     private fun updatePageIndicator() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 69a7ec3..b3e6682 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
 
 import android.app.PendingIntent;
+import android.app.WallpaperColors;
 import android.app.smartspace.SmartspaceAction;
 import android.content.Context;
 import android.content.Intent;
@@ -40,6 +41,7 @@
 import android.view.ViewGroup;
 import android.widget.ImageButton;
 import android.widget.ImageView;
+import android.widget.SeekBar;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -54,6 +56,7 @@
 import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
@@ -405,7 +408,7 @@
         seamlessView.setContentDescription(deviceString);
 
         // Dismiss
-        mMediaViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
+        mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
         mMediaViewHolder.getDismiss().setEnabled(isDismissible);
         mMediaViewHolder.getDismiss().setOnClickListener(v -> {
             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
@@ -438,11 +441,10 @@
         ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
 
         // Album art
-        PlayerViewHolder playerHolder = (PlayerViewHolder) mMediaViewHolder;
-        ImageView albumView = playerHolder.getAlbumView();
+        ImageView albumView = mMediaViewHolder.getAlbumView();
         boolean hasArtwork = data.getArtwork() != null;
         if (hasArtwork) {
-            Drawable artwork = scaleDrawable(data.getArtwork());
+            Drawable artwork = getScaledThumbnail(data.getArtwork());
             albumView.setPadding(0, 0, 0, 0);
             albumView.setImageDrawable(artwork);
         } else {
@@ -548,6 +550,19 @@
 
     /** Bind elements specific to PlayerSessionViewHolder */
     private void bindSessionPlayer(@NonNull MediaData data, String key) {
+        // Default colors
+        int surfaceColor = mBackgroundColor;
+        int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
+                com.android.internal.R.attr.textColorPrimary).getDefaultColor();
+        int textPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
+                com.android.internal.R.attr.textColorPrimary).getDefaultColor();
+        int textPrimaryInverse = com.android.settingslib.Utils.getColorAttr(mContext,
+                com.android.internal.R.attr.textColorPrimaryInverse).getDefaultColor();
+        int textSecondary = com.android.settingslib.Utils.getColorAttr(mContext,
+                com.android.internal.R.attr.textColorSecondary).getDefaultColor();
+        int textTertiary = com.android.settingslib.Utils.getColorAttr(mContext,
+                com.android.internal.R.attr.textColorTertiary).getDefaultColor();
+
         // App icon - use launcher icon
         ImageView appIconView = mMediaViewHolder.getAppIcon();
         appIconView.clearColorFilter();
@@ -567,26 +582,106 @@
             appIconView.setColorFilter(color);
         }
 
+        // Album art
+        ColorScheme colorScheme = null;
+        ImageView albumView = mMediaViewHolder.getAlbumView();
+        boolean hasArtwork = data.getArtwork() != null;
+        if (hasArtwork) {
+            colorScheme = new ColorScheme(WallpaperColors.fromBitmap(data.getArtwork().getBitmap()),
+                        true);
+
+            // Scale artwork to fit background
+            int width = mMediaViewHolder.getPlayer().getWidth();
+            int height = mMediaViewHolder.getPlayer().getHeight();
+            Drawable artwork = getScaledBackground(data.getArtwork(), width, height);
+            albumView.setPadding(0, 0, 0, 0);
+            albumView.setImageDrawable(artwork);
+            albumView.setClipToOutline(true);
+        } else {
+            // If there's no artwork, use colors from the app icon
+            try {
+                Drawable icon = mContext.getPackageManager().getApplicationIcon(
+                        data.getPackageName());
+                colorScheme = new ColorScheme(WallpaperColors.fromDrawable(icon), true);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e);
+            }
+        }
+
+        // Get colors for player
+        if (colorScheme != null) {
+            surfaceColor = colorScheme.getAccent2().get(9); // A2-800
+            accentPrimary = colorScheme.getAccent1().get(2); // A1-100
+            textPrimary = colorScheme.getNeutral1().get(1); // N1-50
+            textPrimaryInverse = colorScheme.getNeutral1().get(10); // N1-900
+            textSecondary = colorScheme.getNeutral2().get(3); // N2-200
+            textTertiary = colorScheme.getNeutral2().get(5); // N2-400
+        }
+
+        ColorStateList bgColorList = ColorStateList.valueOf(surfaceColor);
+        ColorStateList accentColorList = ColorStateList.valueOf(accentPrimary);
+        ColorStateList textColorList = ColorStateList.valueOf(textPrimary);
+
+        // Gradient and background (visible when there is no art)
+        albumView.setForegroundTintList(ColorStateList.valueOf(surfaceColor));
+        albumView.setBackgroundTintList(
+                ColorStateList.valueOf(surfaceColor));
+        mMediaViewHolder.getPlayer().setBackgroundTintList(bgColorList);
+
+        // Metadata text
+        mMediaViewHolder.getTitleText().setTextColor(textPrimary);
+        mMediaViewHolder.getArtistText().setTextColor(textSecondary);
+
+        // Seekbar
+        SeekBar seekbar = mMediaViewHolder.getSeekBar();
+        seekbar.getThumb().setTintList(textColorList);
+        seekbar.setProgressTintList(textColorList);
+        seekbar.setProgressBackgroundTintList(ColorStateList.valueOf(textTertiary));
+
+        // Output switcher
+        View seamlessView = mMediaViewHolder.getSeamlessButton();
+        seamlessView.setBackgroundTintList(accentColorList);
+        ImageView seamlessIconView = mMediaViewHolder.getSeamlessIcon();
+        seamlessIconView.setImageTintList(bgColorList);
+        TextView seamlessText = mMediaViewHolder.getSeamlessText();
+        seamlessText.setTextColor(surfaceColor);
+
         // Media action buttons
         MediaButton semanticActions = data.getSemanticActions();
         if (semanticActions != null) {
             PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
-            setSemanticButton(sessionHolder.getActionPlayPause(),
-                    semanticActions.getPlayOrPause());
-            setSemanticButton(sessionHolder.getActionNext(),
-                    semanticActions.getNextOrCustom());
-            setSemanticButton(sessionHolder.getActionPrev(),
-                    semanticActions.getPrevOrCustom());
-            setSemanticButton(sessionHolder.getActionStart(),
-                    semanticActions.getStartCustom());
-            setSemanticButton(sessionHolder.getActionEnd(),
-                    semanticActions.getEndCustom());
+
+            // Play/pause button has a background
+            sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList);
+            setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(),
+                    ColorStateList.valueOf(textPrimaryInverse));
+
+            setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(),
+                    textColorList);
+            setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(),
+                    textColorList);
+            setSemanticButton(sessionHolder.getActionStart(), semanticActions.getStartCustom(),
+                    textColorList);
+            setSemanticButton(sessionHolder.getActionEnd(), semanticActions.getEndCustom(),
+                    textColorList);
         } else {
             Log.w(TAG, "Using semantic player, but did not get buttons");
         }
+
+        // Long press buttons
+        mMediaViewHolder.getLongPressText().setTextColor(textColorList);
+        mMediaViewHolder.getSettingsText().setTextColor(textColorList);
+        mMediaViewHolder.getSettingsText().setBackgroundTintList(accentColorList);
+        mMediaViewHolder.getCancelText().setTextColor(textColorList);
+        mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList);
+        mMediaViewHolder.getDismissText().setTextColor(textColorList);
+        mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList);
+
     }
 
-    private void setSemanticButton(final ImageButton button, MediaAction mediaAction) {
+    private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
+            ColorStateList fgColor) {
+        button.setImageTintList(fgColor);
         if (mediaAction != null) {
             button.setImageIcon(mediaAction.getIcon());
             button.setContentDescription(mediaAction.getContentDescription());
@@ -844,8 +939,11 @@
         mMediaViewController.openGuts();
     }
 
+    /**
+     * Scale drawable to fit into the square album art thumbnail
+     */
     @UiThread
-    private Drawable scaleDrawable(Icon icon) {
+    private Drawable getScaledThumbnail(Icon icon) {
         if (icon == null) {
             return null;
         }
@@ -870,6 +968,25 @@
     }
 
     /**
+     * Scale artwork to fill the background of the panel
+     */
+    @UiThread
+    private Drawable getScaledBackground(Icon icon, int width, int height) {
+        if (icon == null) {
+            return null;
+        }
+        Drawable drawable = icon.loadDrawable(mContext);
+        Rect bounds = new Rect(0, 0, width, height);
+        if (bounds.width() > width || bounds.height() > height) {
+            float offsetX = (bounds.width() - width) / 2.0f;
+            float offsetY = (bounds.height() - height) / 2.0f;
+            bounds.offset((int) -offsetX, (int) -offsetY);
+        }
+        drawable.setBounds(bounds);
+        return drawable;
+    }
+
+    /**
      * Get the current media controller
      *
      * @return the controller
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index d926e7d..0223c60 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -30,9 +30,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
-import android.graphics.Canvas
 import android.graphics.ImageDecoder
-import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.media.MediaDescription
 import android.media.MediaMetadata
@@ -562,7 +560,7 @@
         val mediaController = mediaControllerFactory.create(token)
         val metadata = mediaController.metadata
 
-        // Foreground and Background colors computed from album art
+        // Album art
         val notif: Notification = sbn.notification
         var artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
         if (artworkBitmap == null) {
@@ -576,24 +574,6 @@
         } else {
             Icon.createWithBitmap(artworkBitmap)
         }
-        if (artWorkIcon != null) {
-            // If we have art, get colors from that
-            if (artworkBitmap == null) {
-                if (artWorkIcon.type == Icon.TYPE_BITMAP ||
-                        artWorkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP) {
-                    artworkBitmap = artWorkIcon.bitmap
-                } else {
-                    val drawable: Drawable = artWorkIcon.loadDrawable(context)
-                    artworkBitmap = Bitmap.createBitmap(
-                            drawable.intrinsicWidth,
-                            drawable.intrinsicHeight,
-                            Bitmap.Config.ARGB_8888)
-                    val canvas = Canvas(artworkBitmap)
-                    drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
-                    drawable.draw(canvas)
-                }
-            }
-        }
 
         // App name
         val builder = Notification.Builder.recoverBuilder(context, notif)
@@ -787,30 +767,28 @@
         return when (action) {
             PlaybackState.ACTION_PLAY -> {
                 MediaAction(
-                    Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_play),
+                    Icon.createWithResource(context, R.drawable.ic_media_play),
                     { controller.transportControls.play() },
                     context.getString(R.string.controls_media_button_play)
                 )
             }
             PlaybackState.ACTION_PAUSE -> {
                 MediaAction(
-                    Icon.createWithResource(context,
-                        com.android.internal.R.drawable.ic_media_pause),
+                    Icon.createWithResource(context, R.drawable.ic_media_pause),
                     { controller.transportControls.pause() },
                     context.getString(R.string.controls_media_button_pause)
                 )
             }
             PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
                 MediaAction(
-                    Icon.createWithResource(context,
-                        com.android.internal.R.drawable.ic_media_previous),
+                    Icon.createWithResource(context, R.drawable.ic_media_prev),
                     { controller.transportControls.skipToPrevious() },
                     context.getString(R.string.controls_media_button_prev)
                 )
             }
             PlaybackState.ACTION_SKIP_TO_NEXT -> {
                 MediaAction(
-                    Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_next),
+                    Icon.createWithResource(context, R.drawable.ic_media_next),
                     { controller.transportControls.skipToNext() },
                     context.getString(R.string.controls_media_button_next)
                 )
@@ -900,7 +878,7 @@
 
     private fun getResumeMediaAction(action: Runnable): MediaAction {
         return MediaAction(
-            Icon.createWithResource(context, R.drawable.lb_ic_play).setTint(themeText),
+            Icon.createWithResource(context, R.drawable.ic_media_play).setTint(themeText),
             action,
             context.getString(R.string.controls_media_resume)
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index c8cd432..6145f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CrossFadeHelper
@@ -82,7 +83,8 @@
     private val notifLockscreenUserManager: NotificationLockscreenUserManager,
     configurationController: ConfigurationController,
     wakefulnessLifecycle: WakefulnessLifecycle,
-    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val dreamOverlayStateController: DreamOverlayStateController
 ) {
 
     /**
@@ -167,7 +169,7 @@
         })
     }
 
-    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
+    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
     /**
      * The last location where this view was at before going to the desired location. This is
      * useful for guided transitions.
@@ -349,6 +351,17 @@
         }
 
     /**
+     * Is the doze animation currently Running
+     */
+    private var dreamOverlayActive: Boolean = false
+        private set(value) {
+            if (field != value) {
+                field = value
+                updateDesiredLocation(forceNoAnimation = true)
+            }
+        }
+
+    /**
      * The current cross fade progress. 0.5f means it's just switching
      * between the start and the end location and the content is fully faded, while 0.75f means
      * that we're halfway faded in again in the target state.
@@ -444,6 +457,12 @@
             }
         })
 
+        dreamOverlayStateController.addCallback(object : DreamOverlayStateController.Callback {
+            override fun onStateChanged() {
+                dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it }
+            }
+        })
+
         wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
             override fun onFinishedGoingToSleep() {
                 goingToSleep = false
@@ -548,8 +567,7 @@
                 previousLocation = this.desiredLocation
             } else if (forceStateUpdate) {
                 val onLockscreen = (!bypassController.bypassEnabled &&
-                        (statusbarState == StatusBarState.KEYGUARD ||
-                            statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+                        (statusbarState == StatusBarState.KEYGUARD))
                 if (desiredLocation == LOCATION_QS && previousLocation == LOCATION_LOCKSCREEN &&
                         !onLockscreen) {
                     // If media active state changed and the device is now unlocked, update the
@@ -936,10 +954,10 @@
             return desiredLocation
         }
         val onLockscreen = (!bypassController.bypassEnabled &&
-            (statusbarState == StatusBarState.KEYGUARD ||
-                statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+            (statusbarState == StatusBarState.KEYGUARD))
         val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
         val location = when {
+            dreamOverlayActive -> LOCATION_DREAM_OVERLAY
             (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
             qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
             !hasActiveMedia -> LOCATION_QS
@@ -1035,6 +1053,11 @@
         const val LOCATION_LOCKSCREEN = 2
 
         /**
+         * Attached on the dream overlay
+         */
+        const val LOCATION_DREAM_OVERLAY = 3
+
+        /**
          * Attached at the root of the hierarchy in an overlay
          */
         const val IN_OVERLAY = -1000
@@ -1062,4 +1085,4 @@
 @IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
     MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
 @Retention(AnnotationRetention.SOURCE)
-annotation class MediaLocation
\ No newline at end of file
+annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
index c333b50..e57b247 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
@@ -35,6 +35,7 @@
     val player = itemView as TransitionLayout
 
     // Player information
+    val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
     val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
     val titleText = itemView.requireViewById<TextView>(R.id.header_title)
     val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
@@ -53,8 +54,9 @@
     // Settings screen
     val longPressText = itemView.requireViewById<TextView>(R.id.remove_text)
     val cancel = itemView.requireViewById<View>(R.id.cancel)
+    val cancelText = itemView.requireViewById<TextView>(R.id.cancel_text)
     val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
-    val dismissLabel = dismiss.getChildAt(0)
+    val dismissText = itemView.requireViewById<TextView>(R.id.dismiss_text)
     val settings = itemView.requireViewById<View>(R.id.settings)
     val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index a1faa40..20b2d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -20,7 +20,6 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageButton
-import android.widget.ImageView
 import android.widget.TextView
 import com.android.systemui.R
 
@@ -29,9 +28,6 @@
  */
 class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) {
 
-    // Player information
-    val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
-
     // Seek bar
     val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time)
     override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 4baef3a..8684509 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -25,11 +25,13 @@
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.media.MediaHostStatesManager;
+import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+import com.android.systemui.media.nearby.NearbyMediaDevicesService;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
 import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 
 import java.util.Optional;
@@ -43,11 +45,14 @@
 import dagger.multibindings.IntoMap;
 
 /** Dagger module for the media package. */
-@Module
+@Module(subcomponents = {
+        MediaComplicationComponent.class,
+})
 public interface MediaModule {
     String QS_PANEL = "media_qs_panel";
     String QUICK_QS_PANEL = "media_quick_qs_panel";
     String KEYGUARD = "media_keyguard";
+    String DREAM = "dream";
 
     /** */
     @Provides
@@ -82,14 +87,25 @@
     /** */
     @Provides
     @SysUISingleton
+    @Named(DREAM)
+    static MediaHost providesDreamMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+            MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+            MediaHostStatesManager statesManager) {
+        return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+    }
+
+    /** */
+    @Provides
+    @SysUISingleton
     static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
             MediaTttFlags mediaTttFlags,
             Context context,
-            WindowManager windowManager) {
+            WindowManager windowManager,
+            CommandQueue commandQueue) {
         if (!mediaTttFlags.isMediaTttEnabled()) {
             return Optional.empty();
         }
-        return Optional.of(new MediaTttChipControllerSender(context, windowManager));
+        return Optional.of(new MediaTttChipControllerSender(context, windowManager, commandQueue));
     }
 
     /** */
@@ -123,9 +139,9 @@
                         mediaTttChipControllerReceiver));
     }
 
-    /** Inject into MediaTttSenderService. */
+    /** Inject into NearbyMediaDevicesService. */
     @Binds
     @IntoMap
-    @ClassKey(MediaTttSenderService.class)
-    Service bindMediaTttSenderService(MediaTttSenderService service);
+    @ClassKey(NearbyMediaDevicesService.class)
+    Service bindMediaNearbyDevicesService(NearbyMediaDevicesService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
new file mode 100644
index 0000000..65c5bc7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import static com.android.systemui.media.dagger.MediaModule.DREAM;
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER;
+
+import android.widget.FrameLayout;
+
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.MediaHostState;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link MediaComplicationViewController} handles connecting the
+ * {@link com.android.systemui.dreams.complication.Complication} view to the {@link MediaHost}.
+ */
+public class MediaComplicationViewController extends ViewController<FrameLayout> {
+    private final MediaHost mMediaHost;
+
+    @Inject
+    public MediaComplicationViewController(
+            @Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout view,
+            @Named(DREAM) MediaHost mediaHost) {
+        super(view);
+        mMediaHost = mediaHost;
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mMediaHost.setExpansion(MediaHostState.COLLAPSED);
+        mMediaHost.setShowsOnlyActiveMedia(true);
+        mMediaHost.setFalsingProtectionNeeded(true);
+        mMediaHost.init(MediaHierarchyManager.LOCATION_DREAM_OVERLAY);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mMediaHost.hostView.setLayoutParams(new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT));
+        mView.addView(mMediaHost.hostView);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mView.removeView(mMediaHost.hostView);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
new file mode 100644
index 0000000..2c35db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+
+import javax.inject.Inject;
+
+/**
+ * Media control complication for dream overlay.
+ */
+public class MediaDreamComplication implements Complication {
+    MediaComplicationComponent.Factory mComponentFactory;
+
+    /**
+     * Default constructor for {@link MediaDreamComplication}.
+     */
+    @Inject
+    public MediaDreamComplication(MediaComplicationComponent.Factory componentFactory) {
+        mComponentFactory = componentFactory;
+    }
+
+    @Override
+    public ViewHolder createView(ComplicationViewModel model) {
+        return mComponentFactory.create().getViewHolder();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
new file mode 100644
index 0000000..8934cd10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.SmartspaceMediaData;
+
+import javax.inject.Inject;
+
+/**
+ * {@link MediaDreamSentinel} is responsible for tracking media state and registering/unregistering
+ * the media complication as appropriate
+ */
+public class MediaDreamSentinel extends CoreStartable {
+    private MediaDataManager.Listener mListener = new MediaDataManager.Listener() {
+        private boolean mAdded;
+        @Override
+        public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {
+        }
+
+        @Override
+        public void onMediaDataRemoved(@NonNull String key) {
+            if (!mAdded) {
+                return;
+            }
+
+            if (mMediaDataManager.hasActiveMedia()) {
+                return;
+            }
+
+            mAdded = false;
+            mDreamOverlayStateController.removeComplication(mComplication);
+        }
+
+        @Override
+        public void onSmartspaceMediaDataLoaded(@NonNull String key,
+                @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
+                boolean isSsReactivated) {
+        }
+
+        @Override
+        public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
+                @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency) {
+            if (mAdded) {
+                return;
+            }
+
+            if (!mMediaDataManager.hasActiveMedia()) {
+                return;
+            }
+
+            mAdded = true;
+            mDreamOverlayStateController.addComplication(mComplication);
+        }
+    };
+
+    private final MediaDataManager mMediaDataManager;
+    private final DreamOverlayStateController mDreamOverlayStateController;
+    private final MediaDreamComplication mComplication;
+
+    @Inject
+    public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager,
+            DreamOverlayStateController dreamOverlayStateController,
+            MediaDreamComplication complication) {
+        super(context);
+        mMediaDataManager = mediaDataManager;
+        mDreamOverlayStateController = dreamOverlayStateController;
+        mComplication = complication;
+    }
+
+    @Override
+    public void start() {
+        mMediaDataManager.addListener(mListener);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java
new file mode 100644
index 0000000..128a38c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java
@@ -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.media.dream;
+
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER;
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_LAYOUT_PARAMS;
+
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link Complication.ViewHolder} implementation for media control.
+ */
+public class MediaViewHolder implements Complication.ViewHolder {
+    private final FrameLayout mContainer;
+    private final MediaComplicationViewController mViewController;
+    private final ComplicationLayoutParams mLayoutParams;
+
+    @Inject
+    MediaViewHolder(@Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout container,
+            MediaComplicationViewController controller,
+            @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) {
+        mContainer = container;
+        mViewController = controller;
+        mViewController.init();
+        mLayoutParams = layoutParams;
+    }
+
+    @Override
+    public View getView() {
+        return mContainer;
+    }
+
+    @Override
+    public ComplicationLayoutParams getLayoutParams() {
+        return mLayoutParams;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
new file mode 100644
index 0000000..3372899
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.media.dream.MediaViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link MediaComplicationComponent} is responsible for generating dependencies surrounding the
+ * media {@link com.android.systemui.dreams.complication.Complication}, such as view controllers
+ * and layout details.
+ */
+@Subcomponent(modules = {
+        MediaComplicationComponent.MediaComplicationModule.class,
+})
+@MediaComplicationComponent.MediaComplicationScope
+public interface MediaComplicationComponent {
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface MediaComplicationScope {}
+
+    /**
+     * Generates {@link MediaComplicationComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        MediaComplicationComponent create();
+    }
+
+    /**
+     * Creates {@link MediaViewHolder}.
+     */
+    MediaViewHolder getViewHolder();
+
+    /**
+     * Scoped values for {@link MediaComplicationComponent}.
+     */
+    @Module
+    interface MediaComplicationModule {
+        String MEDIA_COMPLICATION_CONTAINER = "media_complication_container";
+        String MEDIA_COMPLICATION_LAYOUT_PARAMS = "media_complication_layout_params";
+
+        /**
+         * Provides the complication view.
+         */
+        @Provides
+        @MediaComplicationScope
+        @Named(MEDIA_COMPLICATION_CONTAINER)
+        static FrameLayout provideComplicationContainer(Context context) {
+            return new FrameLayout(context);
+        }
+
+        /**
+         * Provides the layout parameters for the complication view.
+         */
+        @Provides
+        @MediaComplicationScope
+        @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS)
+        static ComplicationLayoutParams provideLayoutParams() {
+            return new ComplicationLayoutParams(0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ComplicationLayoutParams.POSITION_BOTTOM
+                            | ComplicationLayoutParams.POSITION_END,
+                    ComplicationLayoutParams.DIRECTION_UP,
+                    0,
+                    true);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt
deleted file mode 100644
index 0453fdb..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt
+++ /dev/null
@@ -1,51 +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.media.nearby
-
-import com.android.systemui.dagger.SysUISingleton
-
-/**
- * A manager that returns information about devices that are nearby and can receive media transfers.
- */
-@SysUISingleton
-class MediaNearbyDevicesManager {
-
-    /** Returns a list containing the current nearby devices. */
-    fun getCurrentNearbyDevices(): List<NearbyDevice> {
-        // TODO(b/216313420): Implement this function.
-        return emptyList()
-    }
-
-    /**
-     * Registers [callback] to be notified each time a device's range changes or when a new device
-     * comes within range.
-     */
-    fun registerNearbyDevicesCallback(
-        callback: (device: NearbyDevice) -> Unit
-    ) {
-        // TODO(b/216313420): Implement this function.
-    }
-
-    /**
-     * Un-registers [callback]. See [registerNearbyDevicesCallback].
-     */
-    fun unregisterNearbyDevicesCallback(
-        callback: (device: NearbyDevice) -> Unit
-    ) {
-        // TODO(b/216313420): Implement this function.
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt
new file mode 100644
index 0000000..eaf2bd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.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.media.nearby
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider
+import com.android.systemui.shared.media.INearbyMediaDevicesService
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
+import com.android.systemui.shared.media.NearbyDevice
+import javax.inject.Inject
+
+/**
+ * A service that acts as a bridge between (1) external clients that have data on nearby devices
+ * that are able to play media and (2) internal clients (like media Output Switcher) that need data
+ * on these nearby devices.
+ *
+ * TODO(b/216313420): Add logging to this class.
+ */
+@SysUISingleton
+class NearbyMediaDevicesService @Inject constructor() : Service() {
+
+    private var provider: INearbyMediaDevicesProvider? = null
+
+    private val binder: IBinder = object : INearbyMediaDevicesService.Stub() {
+        override fun registerProvider(newProvider: INearbyMediaDevicesProvider) {
+            provider = newProvider
+            newProvider.asBinder().linkToDeath(
+                {
+                    // We might've gotten a new provider before the old provider died, so we only
+                    // need to clear our provider if the most recent provider died.
+                    if (provider == newProvider) {
+                        provider = null
+                    }
+                },
+                /* flags= */ 0
+            )
+        }
+    }
+
+    override fun onBind(intent: Intent?): IBinder = binder
+
+    /** Returns a list containing the current nearby devices. */
+    fun getCurrentNearbyDevices(): List<NearbyDevice> {
+        val currentProvider = provider ?: return emptyList()
+        return currentProvider.currentNearbyDevices
+    }
+
+    /**
+     * Registers [callback] to be notified each time a device's range changes or when a new device
+     * comes within range.
+     */
+    fun registerNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
+        val currentProvider = provider ?: return
+        currentProvider.registerNearbyDevicesCallback(callback)
+    }
+
+    /**
+     * Un-registers [callback]. See [registerNearbyDevicesCallback].
+     */
+    fun unregisterNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
+        val currentProvider = provider ?: return
+        currentProvider.unregisterNearbyDevicesCallback(callback)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt
deleted file mode 100644
index 3c890bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt
+++ /dev/null
@@ -1,44 +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.media.nearby
-
-import androidx.annotation.IntDef
-import kotlin.annotation.AnnotationRetention
-
-@IntDef(
-        RangeZone.RANGE_UNKNOWN,
-        RangeZone.RANGE_FAR,
-        RangeZone.RANGE_LONG,
-        RangeZone.RANGE_CLOSE,
-        RangeZone.RANGE_WITHIN_REACH
-)
-@Retention(AnnotationRetention.SOURCE)
-/** The various range zones a device can be in, in relation to the current device. */
-annotation class RangeZone {
-    companion object {
-        /** Unknown distance range. */
-        const val RANGE_UNKNOWN = 0
-        /** Distance is very far away from the peer device. */
-        const val RANGE_FAR = 1
-        /** Distance is relatively long from the peer device, typically a few meters. */
-        const val RANGE_LONG = 2
-        /** Distance is close to the peer device, typically with one or two meter. */
-        const val RANGE_CLOSE = 3
-        /** Distance is very close to the peer device, typically within one meter or less. */
-        const val RANGE_WITHIN_REACH = 4
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 3720851..bbcbfba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -16,21 +16,16 @@
 
 package com.android.systemui.media.taptotransfer
 
-import android.content.ComponentName
+import android.app.StatusBarManager
 import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
 import android.graphics.Color
 import android.graphics.drawable.Icon
 import android.media.MediaRoute2Info
-import android.os.IBinder
-import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
 import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast
 import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast
 import com.android.systemui.media.taptotransfer.sender.TransferFailed
@@ -38,9 +33,6 @@
 import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded
 import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered
 import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded
-import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderService
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import java.io.PrintWriter
@@ -56,10 +48,7 @@
     private val context: Context,
     private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver,
 ) {
-    private var senderService: IDeviceSenderService? = null
-    private val senderServiceConnection = SenderServiceConnection()
-
-    private val appIconDrawable =
+   private val appIconDrawable =
         Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
             it.setTint(Color.YELLOW)
         }
@@ -75,115 +64,24 @@
     /** All commands for the sender device. */
     inner class SenderCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
-            val otherDeviceName = args[0]
-            val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
-                .addFeature("feature")
-                .build()
-            val otherDeviceInfo = DeviceInfo(otherDeviceName)
+            val routeInfo = MediaRoute2Info.Builder("id", args[0])
+                    .addFeature("feature")
+                    .build()
 
-            when (args[1]) {
-                MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> {
-                    runOnService { senderService ->
-                        senderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
-                    }
-                }
-                MOVE_CLOSER_TO_END_CAST_COMMAND_NAME -> {
-                    runOnService { senderService ->
-                        senderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
-                    }
-                }
-                TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME -> {
-                    runOnService { senderService ->
-                        senderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
-                    }
-                }
-                TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> {
-                    runOnService { senderService ->
-                        senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
-                    }
-                }
-                TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> {
-                    val undoCallback = object : IUndoTransferCallback.Stub() {
-                        override fun onUndoTriggered() {
-                            Log.i(TAG, "Undo transfer to receiver callback triggered")
-                            // The external services that implement this callback would kick off a
-                            // transfer back to this device, so mimic that here.
-                            runOnService { senderService ->
-                                senderService
-                                    .transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
-                            }
-                        }
-                    }
-                    runOnService { senderService ->
-                        senderService
-                            .transferToReceiverSucceeded(mediaInfo, otherDeviceInfo, undoCallback)
-                    }
-                }
-                TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME -> {
-                    val undoCallback = object : IUndoTransferCallback.Stub() {
-                        override fun onUndoTriggered() {
-                            Log.i(TAG, "Undo transfer to this device callback triggered")
-                            // The external services that implement this callback would kick off a
-                            // transfer back to the receiver, so mimic that here.
-                            runOnService { senderService ->
-                                senderService
-                                    .transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
-                            }
-                        }
-                    }
-                    runOnService { senderService ->
-                        senderService
-                            .transferToThisDeviceSucceeded(mediaInfo, otherDeviceInfo, undoCallback)
-                    }
-                }
-                TRANSFER_FAILED_COMMAND_NAME -> {
-                    runOnService { senderService ->
-                        senderService.transferFailed(mediaInfo, otherDeviceInfo)
-                    }
-                }
-                NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME -> {
-                    runOnService { senderService ->
-                        senderService.noLongerCloseToReceiver(mediaInfo, otherDeviceInfo)
-                        context.unbindService(senderServiceConnection)
-                    }
-                }
-                else -> {
-                    pw.println("Sender command must be one of " +
-                            "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " +
-                            "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " +
-                            "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " +
-                            "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " +
-                            "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " +
-                            "$TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME, " +
-                            "$TRANSFER_FAILED_COMMAND_NAME, " +
-                            NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME
-                    )
-                }
-            }
+            val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
+                    as StatusBarManager
+            statusBarManager.updateMediaTapToTransferSenderDisplay(
+                    StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+                    routeInfo,
+                    /* undoExecutor= */ null,
+                    /* undoCallback= */ null
+            )
+            // TODO(b/216318437): Migrate the rest of the callbacks to StatusBarManager.
         }
 
         override fun help(pw: PrintWriter) {
             pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipStatus>")
         }
-
-        private fun runOnService(command: SenderServiceCommand) {
-            val currentService = senderService
-            if (currentService != null) {
-                command.run(currentService)
-            } else {
-                bindService(command)
-            }
-        }
-
-        private fun bindService(command: SenderServiceCommand) {
-            senderServiceConnection.pendingCommand = command
-            val binding = context.bindService(
-                Intent(context, MediaTttSenderService::class.java),
-                senderServiceConnection,
-                Context.BIND_AUTO_CREATE
-            )
-            Log.i(TAG, "Starting service binding? $binding")
-        }
     }
 
     /** A command to DISPLAY the media ttt chip on the RECEIVER device. */
@@ -207,29 +105,6 @@
             pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_RECEIVER_TAG")
         }
     }
-
-    /** A service connection for [IDeviceSenderService]. */
-    private inner class SenderServiceConnection : ServiceConnection {
-        // A command that should be run when the service gets connected.
-        var pendingCommand: SenderServiceCommand? = null
-
-        override fun onServiceConnected(className: ComponentName, service: IBinder) {
-            val newCallback = IDeviceSenderService.Stub.asInterface(service)
-            senderService = newCallback
-            pendingCommand?.run(newCallback)
-            pendingCommand = null
-        }
-
-        override fun onServiceDisconnected(className: ComponentName) {
-            senderService = null
-        }
-    }
-
-    /** An interface defining a command that should be run on the sender service. */
-    private fun interface SenderServiceCommand {
-        /** Runs the command on the provided [senderService]. */
-        fun run(senderService: IDeviceSenderService)
-    }
 }
 
 @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index c656df2..118a04c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -19,9 +19,9 @@
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.view.View
+import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
 import com.android.systemui.media.taptotransfer.common.MediaTttChipState
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
 
 /**
  * A class that stores all the information necessary to display the media tap-to-transfer chip on
@@ -130,7 +130,7 @@
     appIconDrawable: Drawable,
     appIconContentDescription: String,
     private val otherDeviceName: String,
-    val undoCallback: IUndoTransferCallback? = null
+    val undoCallback: IUndoMediaTransferCallback? = null
 ) : ChipStateSender(appIconDrawable, appIconContentDescription) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
@@ -169,7 +169,7 @@
     appIconDrawable: Drawable,
     appIconContentDescription: String,
     private val otherDeviceName: String,
-    val undoCallback: IUndoTransferCallback? = null
+    val undoCallback: IUndoMediaTransferCallback? = null
 ) : ChipStateSender(appIconDrawable, appIconContentDescription) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_transfer_playing_this_device)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 453e3d6..c510e35 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -16,14 +16,20 @@
 
 package com.android.systemui.media.taptotransfer.sender
 
+import android.app.StatusBarManager
 import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.widget.TextView
+import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
+import com.android.systemui.statusbar.CommandQueue
 import javax.inject.Inject
 
 /**
@@ -34,9 +40,36 @@
 class MediaTttChipControllerSender @Inject constructor(
     context: Context,
     windowManager: WindowManager,
+    private val commandQueue: CommandQueue
 ) : MediaTttChipControllerCommon<ChipStateSender>(
     context, windowManager, R.layout.media_ttt_chip
 ) {
+    // TODO(b/216141276): Use app icon from media route info instead of this fake one.
+    private val fakeAppIconDrawable =
+        Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
+            it.setTint(Color.YELLOW)
+        }
+
+    private val commandQueueCallback = object : CommandQueue.Callbacks {
+        override fun updateMediaTapToTransferSenderDisplay(
+                @StatusBarManager.MediaTransferSenderState displayState: Int,
+                routeInfo: MediaRoute2Info,
+                undoCallback: IUndoMediaTransferCallback?
+        ) {
+            // TODO(b/216318437): Trigger displayChip with the right state based on displayState.
+            displayChip(
+                MoveCloserToStartCast(
+                    // TODO(b/217418566): This app icon content description is incorrect --
+                    //   routeInfo.name is the name of the device, not the name of the app.
+                    fakeAppIconDrawable, routeInfo.name.toString(), routeInfo.name.toString()
+                )
+            )
+        }
+    }
+
+    init {
+        commandQueue.addCallback(commandQueueCallback)
+    }
 
     /** Displays the chip view for the given state. */
     override fun updateChipView(chipState: ChipStateSender, currentChipView: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
deleted file mode 100644
index 717752e..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
+++ /dev/null
@@ -1,182 +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.media.taptotransfer.sender
-
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.graphics.Color
-import android.graphics.drawable.Icon
-import android.media.MediaRoute2Info
-import android.os.IBinder
-import com.android.systemui.R
-import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
-import com.android.systemui.shared.mediattt.IDeviceSenderService
-import javax.inject.Inject
-
-/**
- * Service that allows external handlers to trigger the media chip on the sender device.
- */
-class MediaTttSenderService @Inject constructor(
-    context: Context,
-    val controller: MediaTttChipControllerSender
-) : Service() {
-
-    // TODO(b/203800643): Add logging when callbacks trigger.
-    private val binder: IBinder = object : IDeviceSenderService.Stub() {
-        override fun closeToReceiverToStartCast(
-            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
-        ) {
-            this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
-        }
-
-        override fun closeToReceiverToEndCast(
-            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
-        ) {
-            this@MediaTttSenderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
-        }
-
-        override fun transferFailed(
-            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
-        ) {
-            this@MediaTttSenderService.transferFailed(mediaInfo)
-        }
-
-        override fun transferToReceiverTriggered(
-            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
-        ) {
-            this@MediaTttSenderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
-        }
-
-        override fun transferToThisDeviceTriggered(
-            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
-        ) {
-            this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo)
-        }
-
-        override fun transferToReceiverSucceeded(
-            mediaInfo: MediaRoute2Info,
-            otherDeviceInfo: DeviceInfo,
-            undoCallback: IUndoTransferCallback
-        ) {
-            this@MediaTttSenderService.transferToReceiverSucceeded(
-                mediaInfo, otherDeviceInfo, undoCallback
-            )
-        }
-
-        override fun transferToThisDeviceSucceeded(
-            mediaInfo: MediaRoute2Info,
-            otherDeviceInfo: DeviceInfo,
-            undoCallback: IUndoTransferCallback
-        ) {
-            this@MediaTttSenderService.transferToThisDeviceSucceeded(
-                mediaInfo, otherDeviceInfo, undoCallback
-            )
-        }
-
-        override fun noLongerCloseToReceiver(
-            mediaInfo: MediaRoute2Info,
-            otherDeviceInfo: DeviceInfo
-        ) {
-            this@MediaTttSenderService.noLongerCloseToReceiver()
-        }
-    }
-
-    // TODO(b/203800643): Use the app icon from the media info instead of a fake one.
-    private val fakeAppIconDrawable =
-        Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
-            it.setTint(Color.YELLOW)
-        }
-
-    override fun onBind(intent: Intent?): IBinder = binder
-
-    private fun closeToReceiverToStartCast(
-        mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
-    ) {
-        val chipState = MoveCloserToStartCast(
-            appIconDrawable = fakeAppIconDrawable,
-            appIconContentDescription = mediaInfo.name.toString(),
-            otherDeviceName = otherDeviceInfo.name
-        )
-        controller.displayChip(chipState)
-    }
-
-    private fun closeToReceiverToEndCast(mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo) {
-        val chipState = MoveCloserToEndCast(
-            appIconDrawable = fakeAppIconDrawable,
-            appIconContentDescription = mediaInfo.name.toString(),
-            otherDeviceName = otherDeviceInfo.name
-        )
-        controller.displayChip(chipState)
-    }
-
-    private fun transferFailed(mediaInfo: MediaRoute2Info) {
-        val chipState = TransferFailed(
-            appIconDrawable = fakeAppIconDrawable,
-            appIconContentDescription = mediaInfo.name.toString()
-        )
-        controller.displayChip(chipState)
-    }
-
-    private fun transferToReceiverTriggered(
-        mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
-    ) {
-        val chipState = TransferToReceiverTriggered(
-            appIconDrawable = fakeAppIconDrawable,
-            appIconContentDescription = mediaInfo.name.toString(),
-            otherDeviceName = otherDeviceInfo.name
-        )
-        controller.displayChip(chipState)
-    }
-
-    private fun transferToThisDeviceTriggered(mediaInfo: MediaRoute2Info) {
-        val chipState = TransferToThisDeviceTriggered(
-            appIconDrawable = fakeAppIconDrawable,
-            appIconContentDescription = mediaInfo.name.toString()
-        )
-        controller.displayChip(chipState)
-    }
-
-    private fun transferToReceiverSucceeded(
-        mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback
-    ) {
-        val chipState = TransferToReceiverSucceeded(
-            appIconDrawable = fakeAppIconDrawable,
-            appIconContentDescription = mediaInfo.name.toString(),
-            otherDeviceName = otherDeviceInfo.name,
-            undoCallback = undoCallback
-        )
-        controller.displayChip(chipState)
-    }
-
-    private fun transferToThisDeviceSucceeded(
-        mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback
-    ) {
-        val chipState = TransferToThisDeviceSucceeded(
-            appIconDrawable = fakeAppIconDrawable,
-            appIconContentDescription = mediaInfo.name.toString(),
-            otherDeviceName = otherDeviceInfo.name,
-            undoCallback = undoCallback
-        )
-        controller.displayChip(chipState)
-    }
-
-    private fun noLongerCloseToReceiver() {
-        controller.removeChip()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 4f4bd1e..be45a62 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -104,7 +104,8 @@
     private static final int MAX_NUM_LOGGED_PREDICTIONS = 10;
     private static final int MAX_NUM_LOGGED_GESTURES = 10;
 
-    static final boolean DEBUG_MISSING_GESTURE = false;
+    // Temporary log until b/202433017 is resolved
+    static final boolean DEBUG_MISSING_GESTURE = true;
     static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
 
     private ISystemGestureExclusionListener mGestureExclusionListener =
@@ -318,7 +319,11 @@
             String recentsPackageName = recentsComponentName.getPackageName();
             PackageManager manager = context.getPackageManager();
             try {
-                Resources resources = manager.getResourcesForApplication(recentsPackageName);
+                Resources resources = manager.getResourcesForApplication(
+                        manager.getApplicationInfo(recentsPackageName,
+                                PackageManager.MATCH_UNINSTALLED_PACKAGES
+                                        | PackageManager.MATCH_DISABLED_COMPONENTS
+                                        | PackageManager.GET_SHARED_LIBRARY_FILES));
                 int resId = resources.getIdentifier(
                         "gesture_blocking_activities", "array", recentsPackageName);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 7ac9205..4aedbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -29,14 +29,16 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.logging.nano.MetricsProto
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED
-import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED
 import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.qs.dagger.QSScope
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.phone.SettingsButton
@@ -54,13 +56,14 @@
  * Main difference between QS and QQS behaviour is condition when buttons should be visible,
  * determined by [buttonsVisibleState]
  */
+@QSScope
 class FooterActionsController @Inject constructor(
     view: FooterActionsView,
+    multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
     private val activityStarter: ActivityStarter,
     private val userManager: UserManager,
     private val userTracker: UserTracker,
     private val userInfoController: UserInfoController,
-    private val multiUserSwitchController: MultiUserSwitchController,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val falsingManager: FalsingManager,
     private val metricsLogger: MetricsLogger,
@@ -68,20 +71,34 @@
     private val globalActionsDialog: GlobalActionsDialogLite,
     private val uiEventLogger: UiEventLogger,
     @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
-    private val buttonsVisibleState: ExpansionState,
     private val globalSetting: GlobalSettings,
-    private val handler: Handler
+    private val handler: Handler,
+    private val featureFlags: FeatureFlags
 ) : ViewController<FooterActionsView>(view) {
 
-    enum class ExpansionState { COLLAPSED, EXPANDED }
-
+    private var lastExpansion = -1f
     private var listening: Boolean = false
 
-    var expanded = false
+    private val alphaAnimator = TouchAnimator.Builder()
+            .addFloat(mView, "alpha", 0f, 1f)
+            .setStartDelay(0.9f)
+            .build()
+
+    var visible = true
+        set(value) {
+            field = value
+            updateVisibility()
+        }
+
+    init {
+        view.elevation = resources.displayMetrics.density * 4f
+        view.setBackgroundColor(Utils.getColorAttrDefaultColor(context, R.attr.underSurfaceColor))
+    }
 
     private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
     private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
     private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
+    private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
 
     private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
         val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
@@ -99,9 +116,8 @@
             }
 
     private val onClickListener = View.OnClickListener { v ->
-        // Don't do anything until views are unhidden. Don't do anything if the tap looks
-        // suspicious.
-        if (!buttonsVisible() || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+        // Don't do anything if the tap looks suspicious.
+        if (!visible || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
             return@OnClickListener
         }
         if (v === settingsButton) {
@@ -110,9 +126,7 @@
                 activityStarter.postQSRunnableDismissingKeyguard {}
                 return@OnClickListener
             }
-            metricsLogger.action(
-                    if (expanded) MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
-                    else MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH)
+            metricsLogger.action(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH)
             if (settingsButton.isTunerClick) {
                 activityStarter.postQSRunnableDismissingKeyguard {
                     if (isTunerEnabled()) {
@@ -135,24 +149,14 @@
         }
     }
 
-    private fun buttonsVisible(): Boolean {
-        return when (buttonsVisibleState) {
-            EXPANDED -> expanded
-            COLLAPSED -> !expanded
-        }
-    }
-
     override fun onInit() {
         multiUserSwitchController.init()
     }
 
-    fun hideFooter() {
-        mView.visibility = View.GONE
-    }
-
-    fun showFooter() {
-        mView.visibility = View.VISIBLE
-        updateView()
+    private fun updateVisibility() {
+        val previousVisibility = mView.visibility
+        mView.visibility = if (visible) View.VISIBLE else View.INVISIBLE
+        if (previousVisibility != mView.visibility) updateView()
     }
 
     private fun startSettingsActivity() {
@@ -204,24 +208,23 @@
     }
 
     fun setExpansion(headerExpansionFraction: Float) {
-        mView.setExpansion(headerExpansionFraction)
-    }
-
-    fun updateAnimator(width: Int, numTiles: Int) {
-        mView.updateAnimator(width, numTiles)
-    }
-
-    fun setKeyguardShowing() {
-        mView.setKeyguardShowing()
-    }
-
-    fun refreshVisibility(shouldBeVisible: Boolean) {
-        if (shouldBeVisible) {
-            showFooter()
+        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            if (headerExpansionFraction != lastExpansion) {
+                if (headerExpansionFraction >= 1f) {
+                    mView.animate().alpha(1f).setDuration(500L).start()
+                } else if (lastExpansion >= 1f && headerExpansionFraction < 1f) {
+                    mView.animate().alpha(0f).setDuration(250L).start()
+                }
+                lastExpansion = headerExpansionFraction
+            }
         } else {
-            hideFooter()
+            alphaAnimator.setPosition(headerExpansionFraction)
         }
     }
 
+    fun setKeyguardShowing(showing: Boolean) {
+        setExpansion(lastExpansion)
+    }
+
     private fun isTunerEnabled() = tunerService.isTunerEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
deleted file mode 100644
index 7694be5..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
+++ /dev/null
@@ -1,74 +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.qs
-
-import android.os.Handler
-import android.os.UserManager
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.globalactions.GlobalActionsDialogLite
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.FooterActionsController.ExpansionState
-import com.android.systemui.qs.dagger.QSFlagsModule
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.MultiUserSwitchController
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.tuner.TunerService
-import com.android.systemui.util.settings.GlobalSettings
-import javax.inject.Inject
-import javax.inject.Named
-
-class FooterActionsControllerBuilder @Inject constructor(
-    private val activityStarter: ActivityStarter,
-    private val userManager: UserManager,
-    private val userTracker: UserTracker,
-    private val userInfoController: UserInfoController,
-    private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
-    private val deviceProvisionedController: DeviceProvisionedController,
-    private val falsingManager: FalsingManager,
-    private val metricsLogger: MetricsLogger,
-    private val tunerService: TunerService,
-    private val globalActionsDialog: GlobalActionsDialogLite,
-    private val uiEventLogger: UiEventLogger,
-    @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
-    private val globalSettings: GlobalSettings,
-    @Main private val handler: Handler
-) {
-    private lateinit var view: FooterActionsView
-    private lateinit var buttonsVisibleState: ExpansionState
-
-    fun withView(view: FooterActionsView): FooterActionsControllerBuilder {
-        this.view = view
-        return this
-    }
-
-    fun withButtonsVisibleWhen(state: ExpansionState): FooterActionsControllerBuilder {
-        buttonsVisibleState = state
-        return this
-    }
-
-    fun build(): FooterActionsController {
-        return FooterActionsController(view, activityStarter, userManager,
-                userTracker, userInfoController, multiUserSwitchControllerFactory.create(view),
-                deviceProvisionedController, falsingManager, metricsLogger, tunerService,
-                globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState,
-                globalSettings, handler)
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index e6fa2ae..18e0cfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -44,8 +44,6 @@
     private lateinit var multiUserAvatar: ImageView
     private lateinit var tunerIcon: View
 
-    private var settingsCogAnimator: TouchAnimator? = null
-
     private var qsDisabled = false
     private var expansionAmount = 0f
 
@@ -66,19 +64,6 @@
         importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
     }
 
-    fun updateAnimator(width: Int, numTiles: Int) {
-        val size = (mContext.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) -
-                mContext.resources.getDimensionPixelSize(R.dimen.qs_tile_padding))
-        val remaining = (width - numTiles * size) / (numTiles - 1)
-        val defSpace = mContext.resources.getDimensionPixelOffset(R.dimen.default_gear_space)
-        val translation = if (isLayoutRtl) (remaining - defSpace) else -(remaining - defSpace)
-        settingsCogAnimator = TouchAnimator.Builder()
-                .addFloat(settingsButton, "translationX", translation.toFloat(), 0f)
-                .addFloat(settingsButton, "rotation", -120f, 0f)
-                .build()
-        setExpansion(expansionAmount)
-    }
-
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         updateResources()
@@ -95,15 +80,6 @@
         tunerIcon.translationX = if (isLayoutRtl) (-tunerIconTranslation) else tunerIconTranslation
     }
 
-    fun setKeyguardShowing() {
-        setExpansion(expansionAmount)
-    }
-
-    fun setExpansion(headerExpansionFraction: Float) {
-        expansionAmount = headerExpansionFraction
-        if (settingsCogAnimator != null) settingsCogAnimator!!.setPosition(headerExpansionFraction)
-    }
-
     fun disable(
         state2: Int,
         isTunerEnabled: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index ded6ae0..d1b569f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -14,9 +14,6 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER;
-
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.util.Log;
@@ -49,7 +46,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /** */
 @QSScope
@@ -88,8 +84,6 @@
     private final QSFgsManagerFooter mFgsManagerFooter;
     private final QSSecurityFooter mSecurityFooter;
     private final QS mQs;
-    private final View mQSFooterActions;
-    private final View mQQSFooterActions;
 
     @Nullable
     private PagedTileLayout mPagedLayout;
@@ -154,16 +148,12 @@
             QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
             QSFgsManagerFooter fgsManagerFooter, QSSecurityFooter securityFooter,
             @Main Executor executor, TunerService tunerService,
-            QSExpansionPathInterpolator qsExpansionPathInterpolator,
-            @Named(QS_FOOTER) FooterActionsView qsFooterActionsView,
-            @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
+            QSExpansionPathInterpolator qsExpansionPathInterpolator) {
         mQs = qs;
         mQuickQsPanel = quickPanel;
         mQsPanelController = qsPanelController;
         mQuickQSPanelController = quickQSPanelController;
         mQuickStatusBarHeader = quickStatusBarHeader;
-        mQQSFooterActions = qqsFooterActionsView;
-        mQSFooterActions = qsFooterActionsView;
         mFgsManagerFooter = fgsManagerFooter;
         mSecurityFooter = securityFooter;
         mHost = qsTileHost;
@@ -476,12 +466,6 @@
                     .setListener(this)
                     .build();
 
-            if (mQQSFooterActions.getVisibility() != View.GONE) {
-                // only when qqs footer is present (which means split shade mode) it needs to
-                // be animated
-                updateQQSFooterAnimation();
-            }
-
             // Fade in the security footer and the divider as we reach the final position
             Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
             builder.addFloat(mFgsManagerFooter.getView(), "alpha", 0, 1);
@@ -627,14 +611,6 @@
         }
     }
 
-    private void updateQQSFooterAnimation() {
-        int translationY = getRelativeTranslationY(mQSFooterActions, mQQSFooterActions);
-        mQQSFooterActionsAnimator = new TouchAnimator.Builder()
-                .addFloat(mQQSFooterActions, "translationY", 0, translationY)
-                .build();
-        mAnimatedQsViews.add(mQSFooterActions);
-    }
-
     private int getRelativeTranslationY(View view1, View view2) {
         int[] qsPosition = new int[2];
         int[] qqsPosition = new int[2];
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index e230e1b..7800027 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -19,10 +19,8 @@
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Path;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.view.View;
@@ -41,7 +39,6 @@
  */
 public class QSContainerImpl extends FrameLayout implements Dumpable {
 
-    private final Point mSizePoint = new Point();
     private int mFancyClippingTop;
     private int mFancyClippingBottom;
     private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
@@ -78,12 +75,6 @@
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        mSizePoint.set(0, 0); // Will be retrieved on next measure pass.
-    }
-
-    @Override
     public boolean performClick() {
         // Want to receive clicks so missing QQS tiles doesn't cause collapse, but
         // don't want to do anything with them.
@@ -152,10 +143,10 @@
     void updateResources(QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController) {
         mQSPanelContainer.setPaddingRelative(
-                getPaddingStart(),
+                mQSPanelContainer.getPaddingStart(),
                 Utils.getQsHeaderSystemIconsAreaHeight(mContext),
-                getPaddingEnd(),
-                getPaddingBottom()
+                mQSPanelContainer.getPaddingEnd(),
+                mQSPanelContainer.getPaddingBottom()
         );
 
         int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
@@ -241,13 +232,6 @@
         }
     }
 
-    private int getDisplayHeight() {
-        if (mSizePoint.y == 0) {
-            getDisplay().getRealSize(mSizePoint);
-        }
-        return mSizePoint.y;
-    }
-
     /**
      * Clip QS bottom using a concave shape.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 0e0681b..aac5672 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -42,11 +42,6 @@
      */
     void setExpansion(float expansion);
 
-    /**
-     * Sets whether or not this footer should set itself to listen for changes in any callbacks
-     * that it has implemented.
-     */
-    void setListening(boolean listening);
 
     /**
      * Sets whether or not the keyguard is currently being shown.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 4622660..6c0ca49 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -46,7 +46,6 @@
 public class QSFooterView extends FrameLayout {
     private PageIndicator mPageIndicator;
     private TextView mBuildText;
-    private View mActionsContainer;
     private View mEditButton;
 
     @Nullable
@@ -78,7 +77,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mPageIndicator = findViewById(R.id.footer_page_indicator);
-        mActionsContainer = requireViewById(R.id.qs_footer_actions);
         mBuildText = findViewById(R.id.build);
         mEditButton = findViewById(android.R.id.edit);
 
@@ -105,10 +103,6 @@
         }
     }
 
-    void updateExpansion() {
-        setExpansion(mExpansionAmount);
-    }
-
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -129,7 +123,6 @@
     @Nullable
     private TouchAnimator createFooterAnimator() {
         TouchAnimator.Builder builder = new TouchAnimator.Builder()
-                .addFloat(mActionsContainer, "alpha", 0, 1)
                 .addFloat(mPageIndicator, "alpha", 0, 1)
                 .addFloat(mBuildText, "alpha", 0, 1)
                 .addFloat(mEditButton, "alpha", 0, 1)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 5327b7e..bef4f43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER;
-
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.text.TextUtils;
@@ -33,7 +31,6 @@
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
  * Controller for {@link QSFooterView}.
@@ -43,8 +40,6 @@
 
     private final UserTracker mUserTracker;
     private final QSPanelController mQsPanelController;
-    private final QuickQSPanelController mQuickQSPanelController;
-    private final FooterActionsController mFooterActionsController;
     private final TextView mBuildText;
     private final PageIndicator mPageIndicator;
     private final View mEditButton;
@@ -56,14 +51,10 @@
             UserTracker userTracker,
             FalsingManager falsingManager,
             ActivityStarter activityStarter,
-            QSPanelController qsPanelController,
-            QuickQSPanelController quickQSPanelController,
-            @Named(QS_FOOTER) FooterActionsController footerActionsController) {
+            QSPanelController qsPanelController) {
         super(view);
         mUserTracker = userTracker;
         mQsPanelController = qsPanelController;
-        mQuickQSPanelController = quickQSPanelController;
-        mFooterActionsController = footerActionsController;
         mFalsingManager = falsingManager;
         mActivityStarter = activityStarter;
 
@@ -73,21 +64,7 @@
     }
 
     @Override
-    protected void onInit() {
-        super.onInit();
-        mFooterActionsController.init();
-    }
-
-    @Override
     protected void onViewAttached() {
-        mView.addOnLayoutChangeListener(
-                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                    mView.updateExpansion();
-                    mFooterActionsController.updateAnimator(right - left,
-                            mQuickQSPanelController.getNumQuickTiles());
-                }
-        );
-
         mBuildText.setOnLongClickListener(view -> {
             CharSequence buildText = mBuildText.getText();
             if (!TextUtils.isEmpty(buildText)) {
@@ -114,9 +91,7 @@
     }
 
     @Override
-    protected void onViewDetached() {
-        setListening(false);
-    }
+    protected void onViewDetached() {}
 
     @Override
     public void setVisibility(int visibility) {
@@ -126,25 +101,17 @@
 
     @Override
     public void setExpanded(boolean expanded) {
-        mFooterActionsController.setExpanded(expanded);
         mView.setExpanded(expanded);
     }
 
     @Override
     public void setExpansion(float expansion) {
         mView.setExpansion(expansion);
-        mFooterActionsController.setExpansion(expansion);
-    }
-
-    @Override
-    public void setListening(boolean listening) {
-        mFooterActionsController.setListening(listening);
     }
 
     @Override
     public void setKeyguardShowing(boolean keyguardShowing) {
         mView.setKeyguardShowing();
-        mFooterActionsController.setKeyguardShowing();
     }
 
     /** */
@@ -156,6 +123,5 @@
     @Override
     public void disable(int state1, int state2, boolean animate) {
         mView.disable(state2);
-        mFooterActionsController.disable(state2);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 259b786..50952bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -118,6 +118,7 @@
     private QSPanelController mQSPanelController;
     private QuickQSPanelController mQuickQSPanelController;
     private QSCustomizerController mQSCustomizerController;
+    private FooterActionsController mQSFooterActionController;
     @Nullable
     private ScrollListener mScrollListener;
     /**
@@ -188,9 +189,11 @@
         QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
         mQSPanelController = qsFragmentComponent.getQSPanelController();
         mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
+        mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
 
         mQSPanelController.init();
         mQuickQSPanelController.init();
+        mQSFooterActionController.init();
 
         mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
         mQSPanelScrollView.addOnLayoutChangeListener(
@@ -380,6 +383,7 @@
         mContainer.disable(state1, state2, animate);
         mHeader.disable(state1, state2, animate);
         mFooter.disable(state1, state2, animate);
+        mQSFooterActionController.disable(state2);
         updateQsState();
     }
 
@@ -396,10 +400,10 @@
                 : View.INVISIBLE);
         mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
                 || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
-        mFooter.setVisibility(!mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating
-                || mShowCollapsedOnKeyguard)
-                ? View.VISIBLE
-                : View.INVISIBLE);
+        boolean footerVisible = !mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating
+                || mShowCollapsedOnKeyguard);
+        mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
+        mQSFooterActionController.setVisible(footerVisible);
         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
                 || (expanded && !mStackScrollerOverscrolling));
         mQSPanelController.setVisibility(
@@ -465,6 +469,7 @@
         }
 
         mFooter.setKeyguardShowing(keyguardShowing);
+        mQSFooterActionController.setKeyguardShowing(keyguardShowing);
         updateQsState();
     }
 
@@ -480,14 +485,13 @@
         if (DEBUG) Log.d(TAG, "setListening " + listening);
         mListening = listening;
         mQSContainerImplController.setListening(listening);
-        mFooter.setListening(listening);
+        mQSFooterActionController.setListening(listening);
         mQSPanelController.setListening(mListening, mQsExpanded);
     }
 
     @Override
     public void setHeaderListening(boolean listening) {
         mQSContainerImplController.setListening(listening);
-        mFooter.setListening(listening);
     }
 
     @Override
@@ -561,6 +565,7 @@
             }
         }
         mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
+        mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
         mQSPanelController.setRevealExpansion(expansion);
         mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
         mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
@@ -711,6 +716,7 @@
         boolean customizing = isCustomizing();
         mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+        mQSFooterActionController.setVisible(!customizing);
         mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         // Let the panel know the position changed and it needs to update where notifications
         // and whatnot are.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 92690c7d..2d2fa1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,7 +17,6 @@
 package com.android.systemui.qs;
 
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER;
 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
 import com.android.internal.logging.MetricsLogger;
@@ -54,7 +53,6 @@
     // brightness is visible only in split shade
     private final QuickQSBrightnessController mBrightnessController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
-    private final FooterActionsController mFooterActionsController;
 
     @Inject
     QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
@@ -63,14 +61,12 @@
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
             DumpManager dumpManager,
-            QuickQSBrightnessController quickQSBrightnessController,
-            @Named(QQS_FOOTER) FooterActionsController footerActionsController
+            QuickQSBrightnessController quickQSBrightnessController
     ) {
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
                 uiEventLogger, qsLogger, dumpManager);
         mBrightnessController = quickQSBrightnessController;
         mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
-        mFooterActionsController = footerActionsController;
     }
 
     @Override
@@ -80,8 +76,6 @@
         mMediaHost.setShowsOnlyActiveMedia(true);
         mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
         mBrightnessController.init(mShouldUseSplitNotificationShade);
-        mFooterActionsController.init();
-        mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade);
     }
 
     @Override
@@ -102,7 +96,6 @@
     void setListening(boolean listening) {
         super.setListening(listening);
         mBrightnessController.setListening(listening);
-        mFooterActionsController.setListening(listening);
     }
 
     public boolean isListening() {
@@ -123,7 +116,6 @@
     @Override
     protected void onConfigurationChanged() {
         mBrightnessController.refreshVisibility(mShouldUseSplitNotificationShade);
-        mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
index 63cbc21..594f4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.dagger;
 
+import com.android.systemui.qs.FooterActionsController;
 import com.android.systemui.qs.QSAnimator;
 import com.android.systemui.qs.QSContainerImplController;
 import com.android.systemui.qs.QSFooter;
@@ -61,4 +62,7 @@
 
     /** Construct a {@link QSSquishinessController}. */
     QSSquishinessController getQSSquishinessController();
+
+    /** Construct a {@link FooterActionsController}. */
+    FooterActionsController getQSFooterActionController();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 1958caf..776ee10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -21,15 +21,15 @@
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewStub;
 
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.privacy.OngoingPrivacyChip;
-import com.android.systemui.qs.FooterActionsController;
-import com.android.systemui.qs.FooterActionsController.ExpansionState;
-import com.android.systemui.qs.FooterActionsControllerBuilder;
 import com.android.systemui.qs.FooterActionsView;
 import com.android.systemui.qs.QSContainerImpl;
 import com.android.systemui.qs.QSFooter;
@@ -55,8 +55,6 @@
 public interface QSFragmentModule {
     String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer";
     String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
-    String QQS_FOOTER = "qqs_footer";
-    String QS_FOOTER = "qs_footer";
     String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
 
     /**
@@ -122,46 +120,26 @@
         return view.findViewById(R.id.qs_footer);
     }
 
-    /** */
+    /**
+     * Provides a {@link FooterActionsView}.
+     *
+     * This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}.
+     */
     @Provides
-    @Named(QS_FOOTER)
-    static FooterActionsView providesQSFooterActionsView(@RootView View view) {
+    static FooterActionsView providesQSFooterActionsView(@RootView View view,
+            FeatureFlags featureFlags) {
+        ViewStub stub;
+        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            stub = view.requireViewById(R.id.container_stub);
+        } else {
+            stub = view.requireViewById(R.id.footer_stub);
+        }
+        stub.inflate();
         return view.findViewById(R.id.qs_footer_actions);
     }
 
     /** */
     @Provides
-    @Named(QQS_FOOTER)
-    static FooterActionsView providesQQSFooterActionsView(@RootView View view) {
-        return view.findViewById(R.id.qqs_footer_actions);
-    }
-
-    /** */
-    @Provides
-    @Named(QQS_FOOTER)
-    static FooterActionsController providesQQSFooterActionsController(
-            FooterActionsControllerBuilder footerActionsControllerBuilder,
-            @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
-        return footerActionsControllerBuilder
-                .withView(qqsFooterActionsView)
-                .withButtonsVisibleWhen(ExpansionState.COLLAPSED)
-                .build();
-    }
-
-    /** */
-    @Provides
-    @Named(QS_FOOTER)
-    static FooterActionsController providesQSFooterActionsController(
-            FooterActionsControllerBuilder footerActionsControllerBuilder,
-            @Named(QS_FOOTER) FooterActionsView qsFooterActionsView) {
-        return footerActionsControllerBuilder
-                .withView(qsFooterActionsView)
-                .withButtonsVisibleWhen(ExpansionState.EXPANDED)
-                .build();
-    }
-
-    /** */
-    @Provides
     @QSScope
     static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
         qsFooterViewController.init();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index dec5afd..c4ea67e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.screenshot;
 
+import static java.util.Objects.requireNonNull;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.drawable.Icon;
@@ -28,10 +30,11 @@
 
 import com.android.systemui.R;
 
+
 /**
  * View for a chip with an icon and text.
  */
-public class ScreenshotActionChip extends FrameLayout {
+public class OverlayActionChip extends FrameLayout {
 
     private static final String TAG = "ScreenshotActionChip";
 
@@ -39,27 +42,27 @@
     private TextView mTextView;
     private boolean mIsPending = false;
 
-    public ScreenshotActionChip(Context context) {
+    public OverlayActionChip(Context context) {
         this(context, null);
     }
 
-    public ScreenshotActionChip(Context context, AttributeSet attrs) {
+    public OverlayActionChip(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
+    public OverlayActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public ScreenshotActionChip(
+    public OverlayActionChip(
             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
     @Override
     protected void onFinishInflate() {
-        mIconView = findViewById(R.id.screenshot_action_chip_icon);
-        mTextView = findViewById(R.id.screenshot_action_chip_text);
+        mIconView = requireNonNull(findViewById(R.id.overlay_action_chip_icon));
+        mTextView = requireNonNull(findViewById(R.id.overlay_action_chip_text));
         updatePadding(mTextView.getText().length() > 0);
     }
 
@@ -116,15 +119,15 @@
                 (LinearLayout.LayoutParams) mTextView.getLayoutParams();
         if (hasText) {
             int paddingHorizontal = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.screenshot_action_chip_padding_horizontal);
+                    R.dimen.overlay_action_chip_padding_horizontal);
             int spacing = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.screenshot_action_chip_spacing);
+                    R.dimen.overlay_action_chip_spacing);
             iconParams.setMarginStart(paddingHorizontal);
             iconParams.setMarginEnd(spacing);
             textParams.setMarginEnd(paddingHorizontal);
         } else {
             int paddingHorizontal = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.screenshot_action_chip_icon_only_padding_horizontal);
+                    R.dimen.overlay_action_chip_icon_only_padding_horizontal);
             iconParams.setMarginStart(paddingHorizontal);
             iconParams.setMarginEnd(paddingHorizontal);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 83d8d19..30456a8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -344,7 +344,7 @@
         };
         mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
                         ClipboardOverlayController.COPY_OVERLAY_ACTION),
-                ClipboardOverlayController.SELF_PERMISSION, null);
+                ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
     }
 
     void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e5649a1..f982790 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -131,9 +131,9 @@
     private final Resources mResources;
     private final Interpolator mFastOutSlowIn;
     private final DisplayMetrics mDisplayMetrics;
-    private final float mCornerSizeX;
-    private final float mDismissDeltaY;
+    private final float mFixedSize;
     private final AccessibilityManager mAccessibilityManager;
+    private final GestureDetector mSwipeDetector;
 
     private int mNavMode;
     private boolean mOrientationPortrait;
@@ -151,23 +151,21 @@
     private LinearLayout mActionsView;
     private ImageView mBackgroundProtection;
     private FrameLayout mDismissButton;
-    private ScreenshotActionChip mShareChip;
-    private ScreenshotActionChip mEditChip;
-    private ScreenshotActionChip mScrollChip;
-    private ScreenshotActionChip mQuickShareChip;
+    private OverlayActionChip mShareChip;
+    private OverlayActionChip mEditChip;
+    private OverlayActionChip mScrollChip;
+    private OverlayActionChip mQuickShareChip;
 
     private UiEventLogger mUiEventLogger;
     private ScreenshotViewCallback mCallbacks;
-    private Animator mDismissAnimation;
     private boolean mPendingSharedTransition;
-    private GestureDetector mSwipeDetector;
     private SwipeDismissHandler mSwipeDismissHandler;
     private InputMonitorCompat mInputMonitor;
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
     private boolean mShowScrollablePreview;
     private String mPackageName = "";
 
-    private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
+    private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
     private PendingInteraction mPendingInteraction;
 
     private enum PendingInteraction {
@@ -194,9 +192,7 @@
         super(context, attrs, defStyleAttr, defStyleRes);
         mResources = mContext.getResources();
 
-        mCornerSizeX = mResources.getDimensionPixelSize(R.dimen.screenshot_x_scale);
-        mDismissDeltaY = mResources.getDimensionPixelSize(
-                R.dimen.screenshot_dismissal_height_delta);
+        mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale);
 
         // standard material ease
         mFastOutSlowIn =
@@ -474,16 +470,14 @@
         int orientation = mContext.getResources().getConfiguration().orientation;
         mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
         updateInsets(insets);
-        int screenshotFixedSize =
-                mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale);
         ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
         if (mOrientationPortrait) {
-            params.width = screenshotFixedSize;
+            params.width = (int) mFixedSize;
             params.height = LayoutParams.WRAP_CONTENT;
             mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_START);
         } else {
             params.width = LayoutParams.WRAP_CONTENT;
-            params.height = screenshotFixedSize;
+            params.height = (int) mFixedSize;
             mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_END);
         }
 
@@ -500,7 +494,7 @@
 
         // ratio of preview width, end vs. start size
         float cornerScale =
-                mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
+                mFixedSize / (mOrientationPortrait ? bounds.width() : bounds.height());
         final float currentScale = 1 / cornerScale;
 
         AnimatorSet dropInAnimation = new AnimatorSet();
@@ -651,7 +645,7 @@
         } catch (RemoteException e) {
         }
 
-        ArrayList<ScreenshotActionChip> chips = new ArrayList<>();
+        ArrayList<OverlayActionChip> chips = new ArrayList<>();
 
         mShareChip.setContentDescription(mContext.getString(R.string.screenshot_share_description));
         mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
@@ -716,7 +710,7 @@
                     + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
             mActionsContainer.setScaleX(containerScale);
             mActionsContainerBackground.setScaleX(containerScale);
-            for (ScreenshotActionChip chip : chips) {
+            for (OverlayActionChip chip : chips) {
                 chip.setAlpha(t);
                 chip.setScaleX(1 / containerScale); // invert to keep size of children constant
             }
@@ -772,8 +766,8 @@
             LayoutInflater inflater = LayoutInflater.from(mContext);
 
             for (Notification.Action smartAction : imageData.smartActions) {
-                ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
-                        R.layout.screenshot_action_chip, mActionsView, false);
+                OverlayActionChip actionChip = (OverlayActionChip) inflater.inflate(
+                        R.layout.overlay_action_chip, mActionsView, false);
                 actionChip.setText(smartAction.title);
                 actionChip.setIcon(smartAction.getIcon(), false);
                 actionChip.setPendingIntent(smartAction.actionIntent,
@@ -792,8 +786,8 @@
     void addQuickShareChip(Notification.Action quickShareAction) {
         if (mPendingInteraction == null) {
             LayoutInflater inflater = LayoutInflater.from(mContext);
-            mQuickShareChip = (ScreenshotActionChip) inflater.inflate(
-                    R.layout.screenshot_action_chip, mActionsView, false);
+            mQuickShareChip = (OverlayActionChip) inflater.inflate(
+                    R.layout.overlay_action_chip, mActionsView, false);
             mQuickShareChip.setText(quickShareAction.title);
             mQuickShareChip.setIcon(quickShareAction.getIcon(), false);
             mQuickShareChip.setOnClickListener(v -> {
@@ -894,7 +888,7 @@
         if (mShowScrollablePreview) {
             Rect scrollableArea = scrollableAreaOnScreen(response);
 
-            float scale = mCornerSizeX
+            float scale = mFixedSize
                     / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
             ConstraintLayout.LayoutParams params =
                     (ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
@@ -945,7 +939,7 @@
     }
 
     boolean isDismissing() {
-        return (mDismissAnimation != null && mDismissAnimation.isRunning());
+        return mSwipeDismissHandler.isDismissing();
     }
 
     boolean isPendingSharedTransition() {
@@ -961,12 +955,6 @@
             Log.d(TAG, "reset screenshot view");
         }
 
-        if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
-            if (DEBUG_ANIM) {
-                Log.d(TAG, "cancelling dismiss animation");
-            }
-            mDismissAnimation.cancel();
-        }
         mSwipeDismissHandler.cancel();
         if (DEBUG_WINDOW) {
             Log.d(TAG, "removing OnComputeInternalInsetsListener");
@@ -994,7 +982,7 @@
         mShareChip.setIsPending(false);
         mEditChip.setIsPending(false);
         mPendingInteraction = null;
-        for (ScreenshotActionChip chip : mSmartChips) {
+        for (OverlayActionChip chip : mSmartChips) {
             mActionsView.removeView(chip);
         }
         mSmartChips.clear();
@@ -1019,31 +1007,6 @@
         }
     }
 
-    private AnimatorSet createScreenshotTranslateDismissAnimation() {
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
-        alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
-        alphaAnim.addUpdateListener(animation -> {
-            setAlpha(1 - animation.getAnimatedFraction());
-        });
-
-        ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1);
-        xAnim.setInterpolator(mAccelerateInterpolator);
-        xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
-        float deltaX = mDirectionLTR
-                ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
-                : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
-        xAnim.addUpdateListener(animation -> {
-            float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
-            mScreenshotStatic.setTranslationX(currXDelta);
-        });
-
-        AnimatorSet animSet = new AnimatorSet();
-        animSet.play(xAnim).with(alphaAnim);
-
-        return animSet;
-    }
-
     ValueAnimator createScreenshotFadeDismissAnimation() {
         ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
         alphaAnim.addUpdateListener(animation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
index 4e96003..451fb13 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.screenshot;
 
+import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
 
 import android.animation.Animator;
@@ -137,10 +138,20 @@
     }
 
     /**
+     * Return whether the view is currently being dismissed
+     */
+    public boolean isDismissing() {
+        return (mDismissAnimation != null && mDismissAnimation.isRunning());
+    }
+
+    /**
      * Cancel the currently-running dismissal animation, if any.
      */
     public void cancel() {
-        if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+        if (isDismissing()) {
+            if (DEBUG_ANIM) {
+                Log.d(TAG, "cancelling dismiss animation");
+            }
             mDismissAnimation.cancel();
         }
     }
@@ -182,7 +193,13 @@
         // make sure the UI gets all the way off the screen in the direction of movement
         // (the actions container background is guaranteed to be both the leftmost and
         // rightmost UI element in LTR and RTL)
-        float finalX = startX <= 0 ? -1 * mView.getRight() : mDisplayMetrics.widthPixels;
+        float finalX;
+        int layoutDir = mView.getContext().getResources().getConfiguration().getLayoutDirection();
+        if (startX > 0 || (startX == 0 && layoutDir == View.LAYOUT_DIRECTION_RTL)) {
+            finalX = mDisplayMetrics.widthPixels;
+        } else {
+            finalX = -1 * mView.getRight();
+        }
         float distance = Math.abs(finalX - startX);
 
         anim.addUpdateListener(animation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 597e424..2f5eaa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -37,11 +37,13 @@
 import android.graphics.drawable.Icon;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.media.MediaRoute2Info;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -62,6 +64,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IAddTileResultCallback;
 import com.android.internal.statusbar.IStatusBar;
+import com.android.internal.statusbar.IUndoMediaTransferCallback;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.GcUtils;
 import com.android.internal.view.AppearanceRegion;
@@ -154,6 +157,9 @@
     private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT;
+    private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT;
+    private static final int MSG_MEDIA_TRANSFER_SENDER_STATE = 64 << MSG_SHIFT;
+    private static final int MSG_MEDIA_TRANSFER_RECEIVER_STATE = 65 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -317,6 +323,12 @@
         }
 
         /**
+         * @see IStatusBar#setBiometicContextListener(IBiometricContextListener)
+         */
+        default void setBiometicContextListener(IBiometricContextListener listener) {
+        }
+
+        /**
          * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
          */
         default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
@@ -431,6 +443,17 @@
          * @see IStatusBar#cancelRequestAddTile
          */
         default void cancelRequestAddTile(@NonNull String packageName) {}
+
+        /** @see IStatusBar#updateMediaTapToTransferSenderDisplay */
+        default void updateMediaTapToTransferSenderDisplay(
+                @StatusBarManager.MediaTransferSenderState int displayState,
+                @NonNull MediaRoute2Info routeInfo,
+                @Nullable IUndoMediaTransferCallback undoCallback) {}
+
+        /** @see IStatusBar#updateMediaTapToTransferReceiverDisplay */
+        default void updateMediaTapToTransferReceiverDisplay(
+                @StatusBarManager.MediaTransferReceiverState int displayState,
+                @NonNull MediaRoute2Info routeInfo) {}
     }
 
     public CommandQueue(Context context) {
@@ -958,6 +981,13 @@
     }
 
     @Override
+    public void setBiometicContextListener(IBiometricContextListener listener) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_SET_BIOMETRICS_LISTENER, listener).sendToTarget();
+        }
+    }
+
+    @Override
     public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
         synchronized (mLock) {
             mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
@@ -1162,6 +1192,29 @@
         mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_CANCEL, s).sendToTarget();
     }
 
+    @Override
+    public void updateMediaTapToTransferSenderDisplay(
+            @StatusBarManager.MediaTransferSenderState int displayState,
+            MediaRoute2Info routeInfo,
+            IUndoMediaTransferCallback undoCallback
+    ) throws RemoteException {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = displayState;
+        args.arg2 = routeInfo;
+        args.arg3 = undoCallback;
+        mHandler.obtainMessage(MSG_MEDIA_TRANSFER_SENDER_STATE, args).sendToTarget();
+    }
+
+    @Override
+    public void updateMediaTapToTransferReceiverDisplay(
+            int displayState,
+            MediaRoute2Info routeInfo) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = displayState;
+        args.arg2 = routeInfo;
+        mHandler.obtainMessage(MSG_MEDIA_TRANSFER_RECEIVER_STATE, args).sendToTarget();
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -1411,6 +1464,12 @@
                         mCallbacks.get(i).hideAuthenticationDialog();
                     }
                     break;
+                case MSG_SET_BIOMETRICS_LISTENER:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).setBiometicContextListener(
+                                (IBiometricContextListener) msg.obj);
+                    }
+                    break;
                 case MSG_SET_UDFPS_HBM_LISTENER:
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
@@ -1553,6 +1612,29 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).cancelRequestAddTile(packageName);
                     }
+                    break;
+                case MSG_MEDIA_TRANSFER_SENDER_STATE:
+                    args = (SomeArgs) msg.obj;
+                    int displayState = (int) args.arg1;
+                    MediaRoute2Info routeInfo = (MediaRoute2Info) args.arg2;
+                    IUndoMediaTransferCallback undoCallback =
+                            (IUndoMediaTransferCallback) args.arg3;
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).updateMediaTapToTransferSenderDisplay(
+                                displayState, routeInfo, undoCallback);
+                    }
+                    args.recycle();
+                    break;
+                case MSG_MEDIA_TRANSFER_RECEIVER_STATE:
+                    args = (SomeArgs) msg.obj;
+                    int receiverDisplayState = (int) args.arg1;
+                    MediaRoute2Info receiverRouteInfo = (MediaRoute2Info) args.arg2;
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).updateMediaTapToTransferReceiverDisplay(
+                                receiverDisplayState, receiverRouteInfo);
+                    }
+                    args.recycle();
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c136d9c..b312ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -337,11 +337,11 @@
             if (field != value || forceApplyAmount) {
                 field = value
                 if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
-                    nsslController.setTransitionToFullShadeAmount(field)
+                    qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+                    nsslController.setTransitionToFullShadeAmount(field, qSDragProgress)
+                    qS.setTransitionToFullShadeAmount(field, qSDragProgress)
                     notificationPanelController.setTransitionToFullShadeAmount(field,
                             false /* animate */, 0 /* delay */)
-                    qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
-                    qS.setTransitionToFullShadeAmount(field, qSDragProgress)
                     // TODO: appear media also in split shade
                     val mediaAmount = if (useSplitShade) 0f else field
                     mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 51a66aa..3411eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.MAX_ICONS_ON_LOCKSCREEN;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -32,6 +34,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -45,6 +48,7 @@
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.Utils;
 
 /**
  * A notification shelf view that is placed inside the notification scroller. It manages the
@@ -81,6 +85,11 @@
     private int mIndexOfFirstViewInShelf = -1;
     private float mCornerAnimationDistance;
     private NotificationShelfController mController;
+    private int mActualWidth = -1;
+    private boolean mUseSplitShade;
+
+    /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
+    private float mFractionToShade;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -122,13 +131,16 @@
         layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
         setLayoutParams(layoutParams);
 
-        int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
+        final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
         mShelfIcons.setPadding(padding, 0, padding, 0);
         mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
         mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
         mCornerAnimationDistance = res.getDimensionPixelSize(
                 R.dimen.notification_corner_animation_distance);
 
+        // TODO(b/213480466)  enable short shelf on split shade
+        mUseSplitShade = Utils.shouldUseSplitNotificationShade(mContext.getResources());
+
         mShelfIcons.setInNotificationIconShelf(true);
         if (!mShowNotificationShelf) {
             setVisibility(GONE);
@@ -203,6 +215,10 @@
 
             final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
             viewState.yTranslation = stackEnd - viewState.height;
+
+            final int shortestWidth = mShelfIcons.calculateWidthFor(MAX_ICONS_ON_LOCKSCREEN);
+            final float fraction = Interpolators.STANDARD.getInterpolation(mFractionToShade);
+            updateStateWidth(viewState, fraction, shortestWidth);
         } else {
             viewState.hidden = true;
             viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -211,6 +227,77 @@
     }
 
     /**
+     * @param shelfState View state for NotificationShelf
+     * @param fraction Fraction of lockscreen to shade transition
+     * @param shortestWidth Shortest width to use for lockscreen shelf
+     */
+    @VisibleForTesting
+    public void updateStateWidth(ShelfState shelfState, float fraction, int shortestWidth) {
+        shelfState.actualWidth = !mUseSplitShade && mAmbientState.isOnKeyguard()
+                ? (int) MathUtils.lerp(shortestWidth, getWidth(), fraction)
+                : getWidth();
+    }
+
+    /**
+     * @param fractionToShade Fraction of lockscreen to shade transition
+     */
+    public void setFractionToShade(float fractionToShade) {
+        mFractionToShade = fractionToShade;
+    }
+
+    /**
+     * @return Actual width of shelf, accounting for possible ongoing width animation
+     */
+    public int getActualWidth() {
+        return mActualWidth > -1 ? mActualWidth : getWidth();
+    }
+
+    /**
+     * @param localX Click x from left of screen
+     * @param slop Margin of error within which we count x for valid click
+     * @param left Left of shelf, from left of screen
+     * @param right Right of shelf, from left of screen
+     * @return Whether click x was in view
+     */
+    @VisibleForTesting
+    public boolean isXInView(float localX, float slop, float left, float right) {
+        return (left - slop) <= localX && localX < (right + slop);
+    }
+
+    /**
+     * @param localY Click y from top of shelf
+     * @param slop Margin of error within which we count y for valid click
+     * @param top Top of shelf
+     * @param bottom Height of shelf
+     * @return Whether click y was in view
+     */
+    @VisibleForTesting
+    public boolean isYInView(float localY, float slop, float top, float bottom) {
+        return (top - slop) <= localY && localY < (bottom + slop);
+    }
+
+    /**
+     * @param localX Click x
+     * @param localY Click y
+     * @param slop Margin of error for valid click
+     * @return Whether this click was on the visible (non-clipped) part of the shelf
+     */
+    @Override
+    public boolean pointInView(float localX, float localY, float slop) {
+        final float containerWidth = getWidth();
+        final float shelfWidth = getActualWidth();
+
+        final float left = isLayoutRtl() ? containerWidth - shelfWidth : 0;
+        final float right = isLayoutRtl() ? containerWidth : shelfWidth;
+
+        final float top = mClipTopAmount;
+        final float bottom = getActualHeight();
+
+        return isXInView(localX, slop, left, right)
+                && isYInView(localY, slop, top, bottom);
+    }
+
+    /**
      * Update the shelf appearance based on the other notifications around it. This transforms
      * the icons from the notification area into the shelf.
      */
@@ -732,11 +819,15 @@
         // we always want to clip to our sides, such that nothing can draw outside of these bounds
         int height = getResources().getDisplayMetrics().heightPixels;
         mClipRect.set(0, -height, getWidth(), height);
-        mShelfIcons.setClipBounds(mClipRect);
+        if (mShelfIcons != null) {
+            mShelfIcons.setClipBounds(mClipRect);
+        }
     }
 
     private void updateRelativeOffset() {
-        mCollapsedIcons.getLocationOnScreen(mTmp);
+        if (mCollapsedIcons != null) {
+            mCollapsedIcons.getLocationOnScreen(mTmp);
+        }
         getLocationOnScreen(mTmp);
     }
 
@@ -831,9 +922,20 @@
         mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
     }
 
-    private class ShelfState extends ExpandableViewState {
+    public class ShelfState extends ExpandableViewState {
         private boolean hasItemsInStableShelf;
         private ExpandableView firstViewInShelf;
+        public int actualWidth = -1;
+
+        private void updateShelfWidth(View view) {
+            if (actualWidth < 0) {
+                return;
+            }
+            mActualWidth = actualWidth;
+            ActivatableNotificationView anv = (ActivatableNotificationView) view;
+            anv.getBackgroundNormal().setActualWidth(actualWidth);
+            mShelfIcons.setActualLayoutWidth(actualWidth);
+        }
 
         @Override
         public void applyToView(View view) {
@@ -846,19 +948,21 @@
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+            updateShelfWidth(view);
         }
 
         @Override
-        public void animateTo(View child, AnimationProperties properties) {
+        public void animateTo(View view, AnimationProperties properties) {
             if (!mShowNotificationShelf) {
                 return;
             }
 
-            super.animateTo(child, properties);
+            super.animateTo(view, properties);
             setIndexOfFirstViewInShelf(firstViewInShelf);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+            updateShelfWidth(view);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 1a1003d..092e86d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -27,6 +27,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -47,6 +48,7 @@
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.Assert;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -98,6 +100,8 @@
     private final ForegroundServiceSectionController mFgsSectionController;
     private final NotifPipelineFlags mNotifPipelineFlags;
     private AssistantFeedbackController mAssistantFeedbackController;
+    private final KeyguardStateController mKeyguardStateController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final Context mContext;
 
     private NotificationPresenter mPresenter;
@@ -129,7 +133,9 @@
             DynamicChildBindController dynamicChildBindController,
             LowPriorityInflationHelper lowPriorityInflationHelper,
             AssistantFeedbackController assistantFeedbackController,
-            NotifPipelineFlags notifPipelineFlags) {
+            NotifPipelineFlags notifPipelineFlags,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardStateController keyguardStateController) {
         mContext = context;
         mHandler = mainHandler;
         mFeatureFlags = featureFlags;
@@ -149,6 +155,8 @@
         mDynamicChildBindController = dynamicChildBindController;
         mLowPriorityInflationHelper = lowPriorityInflationHelper;
         mAssistantFeedbackController = assistantFeedbackController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mKeyguardStateController = keyguardStateController;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -175,6 +183,11 @@
 
         beginUpdate();
 
+        boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked()
+                && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+                && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+                KeyguardUpdateMonitor.getCurrentUser()))
+                && !mKeyguardStateController.isKeyguardGoingAway();
         List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
         ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
         final int N = activeNotifications.size();
@@ -193,7 +206,7 @@
             boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
             boolean userPublic = devicePublic
                     || mLockscreenUserManager.isLockscreenPublicMode(userId);
-            if (userPublic && mDynamicPrivacyController.isDynamicallyUnlocked()
+            if (userPublic && dynamicallyUnlocked
                     && (userId == currentUserId || userId == UserHandle.USER_ALL
                     || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
                 userPublic = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
index c0148c0..16bc951e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
@@ -37,12 +37,6 @@
      */
     public static final int SHADE_LOCKED = 2;
 
-    /**
-     * Status bar is locked and shows the full screen user switcher.
-     */
-    public static final int FULLSCREEN_USER_SWITCHER = 3;
-
-
     public static String toShortString(int x) {
         switch (x) {
             case SHADE:
@@ -51,8 +45,6 @@
                 return "SHD_LCK";
             case KEYGUARD:
                 return "KGRD";
-            case FULLSCREEN_USER_SWITCHER:
-                return "FS_USRSW";
             default:
                 return "bad_value_" + x;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index bd948ece..ee12cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -76,7 +76,7 @@
     // Must be a power of 2
     private static final int HISTORY_SIZE = 32;
 
-    private static final int MAX_STATE = StatusBarState.FULLSCREEN_USER_SWITCHER;
+    private static final int MAX_STATE = StatusBarState.SHADE_LOCKED;
     private static final int MIN_STATE = StatusBarState.SHADE;
 
     private static final Comparator<RankedListener> sComparator =
@@ -518,6 +518,7 @@
     }
 
     private void recordHistoricalState(int newState, int lastState, boolean upcoming) {
+        Trace.traceCounter(Trace.TRACE_TAG_APP, "statusBarState", newState);
         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
         HistoricalState state = mHistoricalRecords[mHistoryIndex];
         state.mNewState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
index 8330169..b66a48e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
@@ -34,10 +34,7 @@
     STATUS_BAR_STATE_KEYGUARD(430),
 
     @UiEvent(doc = "StatusBarState changed to SHADE_LOCKED state")
-    STATUS_BAR_STATE_SHADE_LOCKED(431),
-
-    @UiEvent(doc = "StatusBarState changed to FULLSCREEN_USER_SWITCHER state")
-    STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER(432);
+    STATUS_BAR_STATE_SHADE_LOCKED(431);
 
     private int mId;
     StatusBarStateEvent(int id) {
@@ -60,8 +57,6 @@
                 return STATUS_BAR_STATE_KEYGUARD;
             case StatusBarState.SHADE_LOCKED:
                 return STATUS_BAR_STATE_SHADE_LOCKED;
-            case StatusBarState.FULLSCREEN_USER_SWITCHER:
-                return STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER;
             default:
                 return STATUS_BAR_STATE_UNKNOWN;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index d574cda..e3d0d98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -23,6 +23,7 @@
 import android.service.dreams.IDreamManager;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.SysUISingleton;
@@ -72,6 +73,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.tracing.ProtoTracer;
@@ -214,7 +216,9 @@
             DynamicChildBindController dynamicChildBindController,
             LowPriorityInflationHelper lowPriorityInflationHelper,
             AssistantFeedbackController assistantFeedbackController,
-            NotifPipelineFlags notifPipelineFlags) {
+            NotifPipelineFlags notifPipelineFlags,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardStateController keyguardStateController) {
         return new NotificationViewHierarchyManager(
                 context,
                 mainHandler,
@@ -231,7 +235,9 @@
                 dynamicChildBindController,
                 lowPriorityInflationHelper,
                 assistantFeedbackController,
-                notifPipelineFlags);
+                notifPipelineFlags,
+                keyguardUpdateMonitor,
+                keyguardStateController);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 0ce07cb..22300d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -98,6 +98,8 @@
         setupInvalidateNotifListCallbacks();
         // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
         pipeline.addFinalizeFilter(mNotifFilter);
+
+        updateSectionHeadersVisibility();
     }
 
     private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -164,6 +166,8 @@
         }
     }
 
+    // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+    // these same updates
     private void setupInvalidateNotifListCallbacks() {
         // register onKeyguardShowing callback
         mKeyguardStateController.addCallback(mKeyguardCallback);
@@ -220,10 +224,7 @@
     }
 
     private void invalidateListFromFilter(String reason) {
-        boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-        boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
-        boolean showSections = !onKeyguard && !neverShowSections;
-        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
+        updateSectionHeadersVisibility();
         mNotifFilter.invalidateList();
     }
 
@@ -235,6 +236,13 @@
                         1) == 0;
     }
 
+    private void updateSectionHeadersVisibility() {
+        boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+        boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
+        boolean showSections = !onKeyguard && !neverShowSections;
+        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
+    }
+
     private final KeyguardStateController.Callback mKeyguardCallback =
             new KeyguardStateController.Callback() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index a115e04..9c82cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -17,7 +17,10 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.os.UserHandle
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.DynamicPrivacyController
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,6 +29,7 @@
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Module
 import dagger.Provides
 
@@ -36,9 +40,13 @@
     @CoordinatorScope
     fun provideCoordinator(
         dynamicPrivacyController: DynamicPrivacyController,
-        lockscreenUserManager: NotificationLockscreenUserManager
+        lockscreenUserManager: NotificationLockscreenUserManager,
+        keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        statusBarStateController: StatusBarStateController,
+        keyguardStateController: KeyguardStateController
     ): SensitiveContentCoordinator =
-            SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager)
+            SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager,
+            keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
 }
 
 /** Coordinates re-inflation and post-processing of sensitive notification content. */
@@ -46,7 +54,10 @@
 
 private class SensitiveContentCoordinatorImpl(
     private val dynamicPrivacyController: DynamicPrivacyController,
-    private val lockscreenUserManager: NotificationLockscreenUserManager
+    private val lockscreenUserManager: NotificationLockscreenUserManager,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val statusBarStateController: StatusBarStateController,
+    private val keyguardStateController: KeyguardStateController
 ) : Invalidator("SensitiveContentInvalidator"),
         SensitiveContentCoordinator,
         DynamicPrivacyController.Listener,
@@ -61,6 +72,19 @@
     override fun onDynamicPrivacyChanged(): Unit = invalidateList()
 
     override fun onBeforeRenderList(entries: List<ListEntry>) {
+        if (keyguardStateController.isKeyguardGoingAway() ||
+                statusBarStateController.getState() == StatusBarState.KEYGUARD &&
+                keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+                        KeyguardUpdateMonitor.getCurrentUser())) {
+            // don't update yet if:
+            // - the keyguard is currently going away
+            // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
+
+            // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the
+            // dependent state changes invalidate the pipeline
+            return
+        }
+
         val currentUserId = lockscreenUserManager.currentUserId
         val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
         val deviceSensitive = devicePublic &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 28cd285..386e2d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -104,19 +104,14 @@
                 views.remove(childNode.controller.view)
             }
 
-            if (childCompletelyRemoved && parentSpec == null) {
-                // If both the child and the parent are being removed at the same time, then
-                // keep the child attached to the parent for animation purposes
-                logger.logSkippingDetach(childNode.label, parentNode.label)
-            } else {
-                logger.logDetachingChild(
-                        childNode.label,
-                        !childCompletelyRemoved,
-                        parentNode.label,
-                        newParentNode?.label)
-                parentNode.removeChild(childNode, !childCompletelyRemoved)
-                childNode.parent = null
-            }
+            logger.logDetachingChild(
+                key = childNode.label,
+                isTransfer = !childCompletelyRemoved,
+                isParentRemoved = parentSpec == null,
+                oldParent = parentNode.label,
+                newParent = newParentNode?.label)
+            parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+            childNode.parent = null
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index d274550..4c03572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -28,25 +28,18 @@
     fun logDetachingChild(
         key: String,
         isTransfer: Boolean,
+        isParentRemoved: Boolean,
         oldParent: String?,
         newParent: String?
     ) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = key
             bool1 = isTransfer
+            bool2 = isParentRemoved
             str2 = oldParent
             str3 = newParent
         }, {
-            "Detach $str1 isTransfer=$bool1 oldParent=$str2 newParent=$str3"
-        })
-    }
-
-    fun logSkippingDetach(key: String, parent: String?) {
-        buffer.log(TAG, LogLevel.DEBUG, {
-            str1 = key
-            str2 = parent
-        }, {
-            "Skipping detach of $str1 because its parent $str2 is also being detached"
+            "Detach $str1 isTransfer=$bool1 isParentRemoved=$bool2 oldParent=$str2 newParent=$str3"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 5d6d0f7..fca2aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -162,6 +162,13 @@
         updateBackgroundTint();
     }
 
+    /**
+     * @return The background of this view.
+     */
+    public NotificationBackgroundView getBackgroundNormal() {
+        return mBackgroundNormal;
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
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 dbd22db..1f7d930 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
@@ -49,6 +49,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -1246,6 +1247,7 @@
     }
 
     private void reInflateViews() {
+        Trace.beginSection("ExpandableNotificationRow#reInflateViews");
         // Let's update our childrencontainer. This is intentionally not guarded with
         // mIsSummaryWithChildren since we might have had children but not anymore.
         if (mChildrenContainer != null) {
@@ -1277,6 +1279,7 @@
         RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
         params.setNeedsReinflation(true);
         mRowContentBindStage.requestRebind(mEntry, null /* callback */);
+        Trace.endSection();
     }
 
     @Override
@@ -1737,6 +1740,29 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        Trace.endSection();
+    }
+
+    /** Generates and appends "(MessagingStyle)" type tag to passed string for tracing. */
+    @NonNull
+    private String appendTraceStyleTag(@NonNull String traceTag) {
+        if (!Trace.isEnabled()) {
+            return traceTag;
+        }
+
+        Class<? extends Notification.Style> style =
+                getEntry().getSbn().getNotification().getNotificationStyle();
+        if (style == null) {
+            return traceTag + "(nostyle)";
+        } else {
+            return traceTag + "(" + style.getSimpleName() + ")";
+        }
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mPublicLayout = findViewById(R.id.expandedPublic);
@@ -2542,6 +2568,7 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout"));
         int intrinsicBefore = getIntrinsicHeight();
         super.onLayout(changed, left, top, right, bottom);
         if (intrinsicBefore != getIntrinsicHeight()
@@ -2555,6 +2582,7 @@
         if (mLayoutListener != null) {
             mLayoutListener.onLayout();
         }
+        Trace.endSection();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 0f615aa..c640ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
 
 /**
@@ -39,15 +40,17 @@
     private final boolean mDontModifyCorners;
     private Drawable mBackground;
     private int mClipTopAmount;
-    private int mActualHeight;
     private int mClipBottomAmount;
     private int mTintColor;
     private final float[] mCornerRadii = new float[8];
     private boolean mBottomIsRounded;
     private int mBackgroundTop;
     private boolean mBottomAmountClips = true;
+    private int mActualHeight = -1;
+    private int mActualWidth = -1;
     private boolean mExpandAnimationRunning;
-    private float mActualWidth;
+    private int mExpandAnimationWidth = -1;
+    private int mExpandAnimationHeight = -1;
     private int mDrawableAlpha = 255;
     private boolean mIsPressedAllowed;
 
@@ -59,11 +62,12 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
+        if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop
                 || mExpandAnimationRunning) {
             canvas.save();
             if (!mExpandAnimationRunning) {
-                canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+                canvas.clipRect(0, mClipTopAmount, getWidth(),
+                        getActualHeight() - mClipBottomAmount);
             }
             draw(canvas, mBackground);
             canvas.restore();
@@ -73,17 +77,23 @@
     private void draw(Canvas canvas, Drawable drawable) {
         if (drawable != null) {
             int top = mBackgroundTop;
-            int bottom = mActualHeight;
+            int bottom = getActualHeight();
             if (mBottomIsRounded
                     && mBottomAmountClips
                     && !mExpandAnimationRunning) {
                 bottom -= mClipBottomAmount;
             }
-            int left = 0;
-            int right = getWidth();
+            final boolean isRtl = isLayoutRtl();
+            final int width = getWidth();
+            final int actualWidth = getActualWidth();
+
+            int left = isRtl ? width - actualWidth : 0;
+            int right = isRtl ? width : actualWidth;
+
             if (mExpandAnimationRunning) {
-                left = (int) ((getWidth() - mActualWidth) / 2.0f);
-                right = (int) (left + mActualWidth);
+                // Horizontally center this background view inside of the container
+                left = (int) ((width - actualWidth) / 2.0f);
+                right = (int) (left + actualWidth);
             }
             drawable.setBounds(left, top, right, bottom);
             drawable.draw(canvas);
@@ -152,8 +162,26 @@
         invalidate();
     }
 
-    public int getActualHeight() {
-        return mActualHeight;
+    private int getActualHeight() {
+        if (mExpandAnimationRunning && mExpandAnimationHeight > -1) {
+            return mExpandAnimationHeight;
+        } else if (mActualHeight > -1) {
+            return mActualHeight;
+        }
+        return getHeight();
+    }
+
+    public void setActualWidth(int actualWidth) {
+        mActualWidth = actualWidth;
+    }
+
+    private int getActualWidth() {
+        if (mExpandAnimationRunning && mExpandAnimationWidth > -1) {
+            return mExpandAnimationWidth;
+        } else if (mActualWidth > -1) {
+            return mActualWidth;
+        }
+        return getWidth();
     }
 
     public void setClipTopAmount(int clipTopAmount) {
@@ -241,9 +269,9 @@
     }
 
     /** Set the current expand animation size. */
-    public void setExpandAnimationSize(int actualWidth, int actualHeight) {
-        mActualHeight = actualHeight;
-        mActualWidth = actualWidth;
+    public void setExpandAnimationSize(int width, int height) {
+        mExpandAnimationHeight = width;
+        mExpandAnimationWidth = height;
         invalidate();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 55d66a3..25b8a65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -198,7 +198,7 @@
     private int mMaxTopPadding;
     private int mTopPadding;
     private boolean mAnimateNextTopPaddingChange;
-    private int mBottomMargin;
+    private int mBottomPadding;
     private int mBottomInset = 0;
     private float mQsExpansionFraction;
     private final int mSplitShadeMinContentHeight;
@@ -979,7 +979,7 @@
         mMinTopOverScrollToEscape = res.getDimensionPixelSize(
                 R.dimen.min_top_overscroll_to_qs);
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
-        mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+        mBottomPadding = res.getDimensionPixelSize(R.dimen.notification_panel_padding_bottom);
         mMinimumPaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
         mQsTilePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal);
         mSkinnyNotifsInLandscape = res.getBoolean(R.bool.config_skinnyNotifsInLandscape);
@@ -2278,7 +2278,7 @@
 
         // The topPadding can be bigger than the regular padding when qs is expanded, in that
         // state the maxPanelHeight and the contentHeight should be bigger
-        mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
+        mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomPadding;
         updateScrollability();
         clampScrollPosition();
         updateStackPosition();
@@ -5494,6 +5494,17 @@
     }
 
     /**
+     * @param fraction Fraction of the lockscreen to shade transition. 0f for all other states.
+     *                 Once the lockscreen to shade transition completes and the shade is 100% open
+     *                 LockscreenShadeTransitionController resets fraction to 0
+     *                 where it remains until the next lockscreen-to-shade transition.
+     */
+    public void setFractionToShade(float fraction) {
+        mShelf.setFractionToShade(fraction);
+        requestChildrenUpdate();
+    }
+
+    /**
      * Set a listener to when scrolling changes.
      */
     public void setOnScrollListener(Consumer<Integer> listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 96b1c7d..a2929f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1538,10 +1538,18 @@
     }
 
     /**
-     * Set the amount of pixels we have currently dragged down if we're transitioning to the full
-     * shade. 0.0f means we're not transitioning yet.
+     * @param amount The amount of pixels we have currently dragged down
+     *               for the lockscreen to shade transition. 0f for all other states.
+     * @param fraction The fraction of lockscreen to shade transition.
+     *                 0f for all other states.
+     *
+     * Once the lockscreen to shade transition completes and the shade is 100% open,
+     * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
+     * until the next lockscreen-to-shade transition.
      */
-    public void setTransitionToFullShadeAmount(float amount) {
+    public void setTransitionToFullShadeAmount(float amount, float fraction) {
+        mView.setFractionToShade(fraction);
+
         float extraTopInset = 0.0f;
         if (mStatusBarStateController.getState() == KEYGUARD) {
             float overallProgress = MathUtils.saturate(amount / mView.getHeight());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 8f0579c..e24cd3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -46,7 +46,7 @@
  */
 public class StackScrollAlgorithm {
 
-    public static final float START_FRACTION = 0.3f;
+    public static final float START_FRACTION = 0.5f;
 
     private static final String LOG_TAG = "StackScrollAlgorithm";
     private final ViewGroup mHostView;
@@ -61,7 +61,7 @@
     @VisibleForTesting float mHeadsUpInset;
     private int mPinnedZTranslationExtra;
     private float mNotificationScrimPadding;
-    private int mCloseHandleUnderlapHeight;
+    private int mMarginBottom;
 
     public StackScrollAlgorithm(
             Context context,
@@ -87,7 +87,7 @@
                 R.dimen.heads_up_pinned_elevation);
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
-        mCloseHandleUnderlapHeight = res.getDimensionPixelSize(R.dimen.close_handle_underlap);
+        mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
     }
 
     /**
@@ -463,7 +463,7 @@
             }
         } else {
             if (view instanceof EmptyShadeView) {
-                float fullHeight = ambientState.getLayoutMaxHeight() + mCloseHandleUnderlapHeight
+                float fullHeight = ambientState.getLayoutMaxHeight() + mMarginBottom
                         - ambientState.getStackY();
                 viewState.yTranslation = (fullHeight - getMaxAllowedChildHeight(view)) / 2f;
             } else if (view != ambientState.getTrackedHeadsUpRow()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 0d2bddc..7c9df42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -44,6 +44,7 @@
     public static final int ANIMATION_DURATION_STANDARD = 360;
     public static final int ANIMATION_DURATION_CORNER_RADIUS = 200;
     public static final int ANIMATION_DURATION_WAKEUP = 500;
+    public static final int ANIMATION_DURATION_WAKEUP_SCRIM = 667;
     public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
     public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
     public static final int ANIMATION_DURATION_SWIPE = 200;
@@ -343,9 +344,11 @@
         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
             final ExpandableView changingView = (ExpandableView) event.mChangingView;
             boolean loggable = false;
+            boolean isHeadsUp = false;
             String key = null;
             if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
                 loggable = true;
+                isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp();
                 key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
             }
             if (event.animationType ==
@@ -357,6 +360,9 @@
                     // The position for this child was never generated, let's continue.
                     continue;
                 }
+                if (loggable && isHeadsUp) {
+                    mLogger.logHUNViewAppearingWithAddEvent(key);
+                }
                 viewState.applyToView(changingView);
                 mNewAddChildren.add(changingView);
 
@@ -398,9 +404,18 @@
                     translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
 
                 }
+                Runnable postAnimation = changingView::removeFromTransientContainer;
+                if (loggable && isHeadsUp) {
+                    mLogger.logHUNViewDisappearingWithRemoveEvent(key);
+                    String finalKey = key;
+                    postAnimation = () -> {
+                        mLogger.disappearAnimationEnded(finalKey);
+                        changingView.removeFromTransientContainer();
+                    };
+                }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        0, changingView::removeFromTransientContainer, null);
+                        0, postAnimation, null);
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
                 if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -430,8 +445,7 @@
                 // this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
                 // ADD animations, which would not be logged here.
                 if (loggable) {
-                    mLogger.logHUNViewAppearing(
-                            ((ExpandableNotificationRow) changingView).getEntry().getKey());
+                    mLogger.logHUNViewAppearing(key);
                 }
 
                 mTmpState.applyToView(changingView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 4315265..77377af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -24,6 +24,22 @@
         })
     }
 
+    fun logHUNViewDisappearingWithRemoveEvent(key: String) {
+        buffer.log(TAG, LogLevel.ERROR, {
+            str1 = key
+        }, {
+            "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
+        })
+    }
+
+    fun logHUNViewAppearingWithAddEvent(key: String) {
+        buffer.log(TAG, LogLevel.ERROR, {
+            str1 = key
+        }, {
+            "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
+        })
+    }
+
     fun disappearAnimationEnded(key: String) {
         buffer.log(TAG, LogLevel.INFO, {
             str1 = key
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 04d3e9a..6a78370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -180,6 +180,7 @@
     private final MetricsLogger mMetricsLogger;
     private final AuthController mAuthController;
     private final StatusBarStateController mStatusBarStateController;
+    private final LatencyTracker mLatencyTracker;
 
     private long mLastFpFailureUptimeMillis;
     private int mNumConsecutiveFpFailures;
@@ -281,7 +282,8 @@
             AuthController authController,
             StatusBarStateController statusBarStateController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
-            SessionTracker sessionTracker) {
+            SessionTracker sessionTracker,
+            LatencyTracker latencyTracker) {
         mContext = context;
         mPowerManager = powerManager;
         mShadeController = shadeController;
@@ -289,6 +291,7 @@
         mDozeParameters = dozeParameters;
         mUpdateMonitor.registerCallback(this);
         mMediaManager = notificationMediaManager;
+        mLatencyTracker = latencyTracker;
         wakefulnessLifecycle.addObserver(mWakefulnessObserver);
         screenLifecycle.addObserver(mScreenObserver);
 
@@ -343,13 +346,13 @@
     public void onBiometricAcquired(BiometricSourceType biometricSourceType) {
         Trace.beginSection("BiometricUnlockController#onBiometricAcquired");
         releaseBiometricWakeLock();
-        if (!mUpdateMonitor.isDeviceInteractive()) {
-            if (LatencyTracker.isEnabled(mContext)) {
+        if (isWakeAndUnlock()) {
+            if (mLatencyTracker.isEnabled()) {
                 int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
                 if (biometricSourceType == BiometricSourceType.FACE) {
                     action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK;
                 }
-                LatencyTracker.getInstance(mContext).onActionStart(action);
+                mLatencyTracker.onActionStart(action);
             }
             mWakeLock = mPowerManager.newWakeLock(
                     PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
@@ -652,6 +655,14 @@
         Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
                 .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
 
+        if (mLatencyTracker.isEnabled()) {
+            int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
+            if (biometricSourceType == BiometricSourceType.FACE) {
+                action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK;
+            }
+            mLatencyTracker.onActionCancel(action);
+        }
+
         if (biometricSourceType == BiometricSourceType.FINGERPRINT
                 && mUpdateMonitor.isUdfpsSupported()) {
             long currUptimeMillis = SystemClock.uptimeMillis();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 00b54e9..2ec5f25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
 import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
 
@@ -27,7 +26,6 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.UserManager;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.util.TypedValue;
@@ -47,7 +45,6 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.window.StatusBarWindowView;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -72,7 +69,6 @@
     private StatusIconContainer mStatusIconContainer;
 
     private boolean mKeyguardUserSwitcherEnabled;
-    private final UserManager mUserManager;
 
     private boolean mIsPrivacyDotEnabled;
     private int mSystemIconsSwitcherHiddenExpandedMargin;
@@ -99,10 +95,10 @@
      */
     private int mTopClipping;
     private final Rect mClipRect = new Rect(0, 0, 0, 0);
+    private boolean mIsUserSwitcherEnabled;
 
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mUserManager = UserManager.get(getContext());
     }
 
     @Override
@@ -163,6 +159,10 @@
         updateKeyguardStatusBarHeight();
     }
 
+    public void setUserSwitcherEnabled(boolean enabled) {
+        mIsUserSwitcherEnabled = enabled;
+    }
+
     private void updateKeyguardStatusBarHeight() {
         MarginLayoutParams lp =  (MarginLayoutParams) getLayoutParams();
         lp.height = getStatusBarHeaderHeightKeyguard(mContext);
@@ -200,11 +200,7 @@
             // If we have no keyguard switcher, the screen width is under 600dp. In this case,
             // we only show the multi-user switch if it's enabled through UserManager as well as
             // by the user.
-            // TODO(b/138661450) Move IPC calls to background
-            boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
-                    mContext.getResources().getBoolean(
-                            R.bool.qs_show_user_switcher_for_single_user)));
-            if (isMultiUserEnabled) {
+            if (mIsUserSwitcherEnabled) {
                 mMultiUserAvatar.setVisibility(View.VISIBLE);
             } else {
                 mMultiUserAvatar.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 8187163..ee97fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -23,8 +23,10 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
+import android.os.UserManager;
 import android.util.MathUtils;
 import android.view.View;
 
@@ -92,6 +94,7 @@
     private final BiometricUnlockController mBiometricUnlockController;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final StatusBarContentInsetsProvider mInsetsProvider;
+    private final UserManager mUserManager;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -105,6 +108,11 @@
                     mView.onOverlayChanged();
                     KeyguardStatusBarViewController.this.onThemeChanged();
                 }
+
+                @Override
+                public void onConfigChanged(Configuration newConfig) {
+                    updateUserSwitcher();
+                }
             };
 
     private final SystemStatusAnimationCallback mAnimationCallback =
@@ -159,6 +167,13 @@
                 }
 
                 @Override
+                public void onKeyguardVisibilityChanged(boolean showing) {
+                    if (showing) {
+                        updateUserSwitcher();
+                    }
+                }
+
+                @Override
                 public void onBiometricRunningStateChanged(
                         boolean running,
                         BiometricSourceType biometricSourceType) {
@@ -230,7 +245,8 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             BiometricUnlockController biometricUnlockController,
             SysuiStatusBarStateController statusBarStateController,
-            StatusBarContentInsetsProvider statusBarContentInsetsProvider
+            StatusBarContentInsetsProvider statusBarContentInsetsProvider,
+            UserManager userManager
     ) {
         super(view);
         mCarrierTextController = carrierTextController;
@@ -248,6 +264,7 @@
         mBiometricUnlockController = biometricUnlockController;
         mStatusBarStateController = statusBarStateController;
         mInsetsProvider = statusBarContentInsetsProvider;
+        mUserManager = userManager;
 
         mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
         mKeyguardStateController.addCallback(
@@ -293,7 +310,7 @@
         }
         mView.setOnApplyWindowInsetsListener(
                 (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
-
+        updateUserSwitcher();
         onThemeChanged();
     }
 
@@ -437,6 +454,14 @@
     }
 
     /**
+     * Updates visibility of the user switcher button based on {@link android.os.UserManager} state.
+     */
+    private void updateUserSwitcher() {
+        mView.setUserSwitcherEnabled(mUserManager.isUserSwitcherEnabled(getResources().getBoolean(
+                R.bool.qs_show_user_switcher_for_single_user)));
+    }
+
+    /**
      * Update {@link KeyguardStatusBarView}'s visibility based on whether keyguard is showing and
      * whether heads up is visible.
      */
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 c09c485..ebfed1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -135,7 +135,8 @@
         }
     }.setDuration(CONTENT_FADE_DURATION);
 
-    private static final int MAX_VISIBLE_ICONS_ON_LOCK = 5;
+    private static final int MAX_ICONS_ON_AOD = 3;
+    public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
     public static final int MAX_STATIC_ICONS = 4;
     private static final int MAX_DOTS = 1;
 
@@ -386,6 +387,19 @@
     }
 
     /**
+     * @return Width of shelf for the given number of icons and overflow dot
+     */
+    public int calculateWidthFor(int numMaxIcons) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        return (int) (getActualPaddingStart()
+                + numMaxIcons * mIconSize
+                + mOverflowWidth
+                + getActualPaddingEnd());
+    }
+
+    /**
      * Calculate the horizontal translations for each notification based on how much the icons
      * are inserted into the notification container.
      * If this is not a whole number, the fraction means by how much the icon is appearing.
@@ -394,7 +408,7 @@
         float translationX = getActualPaddingStart();
         int firstOverflowIndex = -1;
         int childCount = getChildCount();
-        int maxVisibleIcons = mOnLockScreen ? MAX_VISIBLE_ICONS_ON_LOCK :
+        int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
                 mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
         float layoutEnd = getLayoutEnd();
         float overflowStart = getMaxOverflowStart();
@@ -414,7 +428,7 @@
             }
             boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
                     && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
-            boolean noOverflowAfter = i == childCount - 1;
+            boolean isLastChild = i == childCount - 1;
             float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
                     ? ((StatusBarIconView) view).getIconScaleIncreased()
                     : 1f;
@@ -423,10 +437,10 @@
                     : StatusBarIconView.STATE_ICON;
 
             boolean isOverflowing =
-                    (translationX > (noOverflowAfter ? layoutEnd - mIconSize
+                    (translationX > (isLastChild ? layoutEnd - mIconSize
                             : overflowStart - mIconSize));
             if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
-                firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
+                firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
                 mVisualOverflowStart = layoutEnd - mOverflowWidth;
                 if (forceOverflow || mIsStaticLayout) {
                     mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
new file mode 100644
index 0000000..ff48755
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
@@ -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 com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.SysUIUnfoldScope
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import javax.inject.Inject
+
+@SysUIUnfoldScope
+class NotificationPanelUnfoldAnimationController
+@Inject
+constructor(private val context: Context, progressProvider: NaturalRotationUnfoldProgressProvider) {
+
+    private val translateAnimator by lazy {
+        UnfoldConstantTranslateAnimator(
+            viewsIdToTranslate =
+                setOf(
+                    ViewIdToTranslate(R.id.quick_settings_panel, LEFT),
+                    ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT),
+                    ViewIdToTranslate(R.id.rightLayout, RIGHT),
+                    ViewIdToTranslate(R.id.clock, LEFT),
+                    ViewIdToTranslate(R.id.date, LEFT)),
+            progressProvider = progressProvider)
+    }
+
+    fun setup(root: ViewGroup) {
+        val translationMax =
+            context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat()
+        translateAnimator.init(root, translationMax)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 9d3f19a..3d3a1da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -20,6 +20,7 @@
 import static android.view.View.GONE;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
 import static androidx.constraintlayout.widget.ConstraintSet.END;
 import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
 import static androidx.constraintlayout.widget.ConstraintSet.START;
@@ -414,6 +415,7 @@
     private int mDisplayTopInset = 0; // in pixels
     private int mDisplayRightInset = 0; // in pixels
     private int mSplitShadeStatusBarHeight;
+    private int mSplitShadeNotificationsScrimMarginBottom;
 
     private final KeyguardClockPositionAlgorithm
             mClockPositionAlgorithm =
@@ -669,6 +671,8 @@
     private boolean mStatusViewCentered = true;
 
     private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
+    private Optional<NotificationPanelUnfoldAnimationController>
+            mNotificationPanelUnfoldAnimationController;
 
     private final ListenerSet<Callbacks> mNotifEventSourceCallbacks = new ListenerSet<>();
 
@@ -929,6 +933,8 @@
 
         mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
         mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
+        mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
+                SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
 
         mCommunalSourceMonitorCallback = (source) -> {
             mUiExecutor.execute(() -> setCommunalSource(source));
@@ -1064,6 +1070,8 @@
 
         mTapAgainViewController.init();
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
+        mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
+                controller.setup(mNotificationContainerParent));
     }
 
     @Override
@@ -1162,6 +1170,9 @@
     public void updateResources() {
         mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
         mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext());
+        mSplitShadeNotificationsScrimMarginBottom =
+                mResources.getDimensionPixelSize(
+                        R.dimen.split_shade_notifications_scrim_margin_bottom);
         int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
         int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
         mShouldUseSplitNotificationShade =
@@ -1171,6 +1182,8 @@
             mQs.setInSplitShade(mShouldUseSplitNotificationShade);
         }
 
+        int notificationsBottomMargin = mResources.getDimensionPixelSize(
+                R.dimen.notification_panel_margin_bottom);
         int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight :
                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
         mSplitShadeHeaderController.setSplitShadeMode(mShouldUseSplitNotificationShade);
@@ -1199,9 +1212,12 @@
         constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth;
         constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth;
         constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin);
+        constraintSet.setMargin(R.id.notification_stack_scroller, BOTTOM,
+                notificationsBottomMargin);
         constraintSet.setMargin(R.id.qs_frame, TOP, topMargin);
         constraintSet.applyTo(mNotificationContainerParent);
         mAmbientState.setStackTopMargin(topMargin);
+        mNotificationsQSContainerController.updateMargins();
         mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
 
         updateKeyguardStatusViewAlignment(/* animate= */false);
@@ -1319,6 +1335,7 @@
         setKeyguardBottomAreaVisibility(mBarState, false);
 
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
+        mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
     }
 
     private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
@@ -2558,7 +2575,8 @@
             right = getView().getRight() + mDisplayRightInset;
         } else {
             top = Math.min(qsPanelBottomY, mSplitShadeStatusBarHeight);
-            bottom = top + mNotificationStackScrollLayoutController.getHeight();
+            bottom = top + mNotificationStackScrollLayoutController.getHeight()
+                    - mSplitShadeNotificationsScrimMarginBottom;
             left = mNotificationStackScrollLayoutController.getLeft();
             right = mNotificationStackScrollLayoutController.getRight();
         }
@@ -4188,9 +4206,10 @@
                     return false;
                 }
 
-                // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able
-                // to pull down QS or expand the shade.
-                if (mStatusBar.isBouncerShowingScrimmed()) {
+                // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+                // otherwise user would be able to pull down QS or expand the shade.
+                if (mStatusBar.isBouncerShowingScrimmed()
+                        || mStatusBar.isBouncerShowingOverDream()) {
                     return false;
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
index 34bb6d3..b457ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -1,6 +1,8 @@
 package com.android.systemui.statusbar.phone
 
 import android.view.WindowInsets
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.qs.QSContainerController
@@ -14,7 +16,8 @@
 class NotificationsQSContainerController @Inject constructor(
     view: NotificationsQuickSettingsContainer,
     private val navigationModeController: NavigationModeController,
-    private val overviewProxyService: OverviewProxyService
+    private val overviewProxyService: OverviewProxyService,
+    private val featureFlags: FeatureFlags
 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
 
     var qsExpanded = false
@@ -63,7 +66,7 @@
     }
 
     public override fun onViewAttached() {
-        notificationsBottomMargin = mView.defaultNotificationsMarginBottom
+        updateMargins()
         overviewProxyService.addCallback(taskbarVisibilityListener)
         mView.setInsetsChangedListener(windowInsetsListener)
         mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
@@ -75,6 +78,10 @@
         mView.removeQSFragmentAttachedListener()
     }
 
+    fun updateMargins() {
+        notificationsBottomMargin = mView.defaultNotificationsMarginBottom
+    }
+
     override fun setCustomizerAnimating(animating: Boolean) {
         if (isQSCustomizerAnimating != animating) {
             isQSCustomizerAnimating = animating
@@ -104,7 +111,11 @@
         }
         mView.setPadding(0, 0, 0, containerPadding)
         mView.setNotificationsMarginBottom(notificationsMargin)
-        mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
+        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            mView.setQSContainerPaddingBottom(notificationsMargin)
+        } else {
+            mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
+        }
     }
 
     private fun calculateBottomSpacing(): Pair<Int, Int> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 9210a8b..95e3b70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -45,7 +45,6 @@
     private View mStackScroller;
     private View mKeyguardStatusBar;
 
-    private int mStackScrollerMargin;
     private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
     private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
     private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
@@ -53,6 +52,7 @@
     private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
     private QS mQs;
     private View mQSScrollView;
+    private View mQSContainer;
 
     public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -63,7 +63,6 @@
         super.onFinishInflate();
         mQsFrame = findViewById(R.id.qs_frame);
         mStackScroller = findViewById(R.id.notification_stack_scroller);
-        mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
     }
 
@@ -72,6 +71,7 @@
         mQs = (QS) fragment;
         mQSFragmentAttachedListener.accept(mQs);
         mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view);
+        mQSContainer = mQs.getView().findViewById(R.id.quick_settings_container);
     }
 
     @Override
@@ -91,13 +91,23 @@
                     mQSScrollView.getPaddingLeft(),
                     mQSScrollView.getPaddingTop(),
                     mQSScrollView.getPaddingRight(),
+                    paddingBottom);
+        }
+    }
+
+    public void setQSContainerPaddingBottom(int paddingBottom) {
+        if (mQSContainer != null) {
+            mQSContainer.setPadding(
+                    mQSContainer.getPaddingLeft(),
+                    mQSContainer.getPaddingTop(),
+                    mQSContainer.getPaddingRight(),
                     paddingBottom
             );
         }
     }
 
     public int getDefaultNotificationsMarginBottom() {
-        return mStackScrollerMargin;
+        return ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
     }
 
     public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index dc6efba..c466a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -73,6 +73,10 @@
 public abstract class PanelViewController {
     public static final boolean DEBUG = PanelView.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
+    public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
+    public static final float FLING_SPEED_UP_FACTOR = 0.6f;
+    public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
+    public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
     private static final int NO_FIXED_DURATION = -1;
     private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
     private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
@@ -269,13 +273,13 @@
         mNotificationShadeWindowController = notificationShadeWindowController;
         mFlingAnimationUtils = flingAnimationUtilsBuilder
                 .reset()
-                .setMaxLengthSeconds(0.6f)
-                .setSpeedUpFactor(0.6f)
+                .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
+                .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
                 .build();
         mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
                 .reset()
-                .setMaxLengthSeconds(0.5f)
-                .setSpeedUpFactor(0.6f)
+                .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
+                .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
                 .build();
         mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
                 .reset()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 1cb19ab..d6bf5f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -49,29 +49,29 @@
     }
 
     override fun onViewAttached() {
-        moveFromCenterAnimationController?.let { animationController ->
-            val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
-            val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+        if (moveFromCenterAnimationController == null) return
 
-            val viewsToAnimate = arrayOf(
-                statusBarLeftSide,
-                systemIconArea
-            )
+        val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
+        val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
 
-            mView.viewTreeObserver.addOnPreDrawListener(object :
-                ViewTreeObserver.OnPreDrawListener {
-                override fun onPreDraw(): Boolean {
-                    animationController.onViewsReady(viewsToAnimate)
-                    mView.viewTreeObserver.removeOnPreDrawListener(this)
-                    return true
-                }
-            })
+        val viewsToAnimate = arrayOf(
+            statusBarLeftSide,
+            systemIconArea
+        )
 
-            mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
-                val widthChanged = right - left != oldRight - oldLeft
-                if (widthChanged) {
-                    moveFromCenterAnimationController.onStatusBarWidthChanged()
-                }
+        mView.viewTreeObserver.addOnPreDrawListener(object :
+            ViewTreeObserver.OnPreDrawListener {
+            override fun onPreDraw(): Boolean {
+                moveFromCenterAnimationController.onViewsReady(viewsToAnimate)
+                mView.viewTreeObserver.removeOnPreDrawListener(this)
+                return true
+            }
+        })
+
+        mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
+            val widthChanged = right - left != oldRight - oldLeft
+            if (widthChanged) {
+                moveFromCenterAnimationController.onStatusBarWidthChanged()
             }
         }
 
@@ -162,9 +162,7 @@
             PhoneStatusBarViewController(
                 view,
                 progressProvider.getOrNull(),
-                unfoldComponent.map {
-                    it.getStatusBarMoveFromCenterAnimationController()
-                }.getOrNull(),
+                unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
                 touchEventHandler,
                 configurationController
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index 091831f..ea61a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -113,14 +113,16 @@
      * the animation ends
      */
     fun shouldDelayKeyguardShow(): Boolean =
-        animations.any { it.shouldPlayAnimation() }
+        animations.any { it.shouldDelayKeyguardShow() }
 
     /**
      * Return true while we want to ignore requests to show keyguard, we need to handle pending
      * keyguard lock requests manually
+     *
+     * @see [com.android.systemui.keyguard.KeyguardViewMediator.maybeHandlePendingLock]
      */
     fun isKeyguardShowDelayed(): Boolean =
-        animations.any { it.isAnimationPlaying() }
+        animations.any { it.isKeyguardShowDelayed() }
 
     /**
      * Return true to ignore requests to hide keyguard
@@ -211,6 +213,8 @@
     fun shouldAnimateInKeyguard(): Boolean = false
     fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
 
+    fun shouldDelayKeyguardShow(): Boolean = false
+    fun isKeyguardShowDelayed(): Boolean = false
     fun isKeyguardHideDelayed(): Boolean = false
     fun shouldHideScrimOnWakeUp(): Boolean = false
     fun overrideNotificationsDozeAmount(): Boolean = false
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 d2e1650..ef5f216 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -62,7 +62,7 @@
         public void prepare(ScrimState previousState) {
             mBlankScreen = false;
             if (previousState == ScrimState.AOD) {
-                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM;
                 if (mDisplayRequiresBlanking) {
                     // DisplayPowerManager will blank the screen, we'll just
                     // set our scrim to black in this frame to avoid flickering and
@@ -70,7 +70,7 @@
                     mBlankScreen = true;
                 }
             } else if (previousState == ScrimState.KEYGUARD) {
-                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM;
             } else {
                 mAnimationDuration = ScrimController.ANIMATION_DURATION;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index c09c3ca..c8cc807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -150,6 +150,7 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.emergency.EmergencyGesture;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -338,6 +339,7 @@
     }
 
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    private final DreamOverlayStateController mDreamOverlayStateController;
     private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
 
     void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
@@ -781,7 +783,8 @@
             ActivityLaunchAnimator activityLaunchAnimator,
             NotifPipelineFlags notifPipelineFlags,
             InteractionJankMonitor jankMonitor,
-            DeviceStateManager deviceStateManager) {
+            DeviceStateManager deviceStateManager,
+            DreamOverlayStateController dreamOverlayStateController) {
         super(context);
         mNotificationsController = notificationsController;
         mFragmentService = fragmentService;
@@ -869,6 +872,7 @@
         mMessageRouter = messageRouter;
         mWallpaperManager = wallpaperManager;
         mJankMonitor = jankMonitor;
+        mDreamOverlayStateController = dreamOverlayStateController;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mStartingSurfaceOptional = startingSurfaceOptional;
@@ -2931,14 +2935,6 @@
         return updateIsKeyguard();
     }
 
-    /**
-     * stop(tag)
-     * @return True if StatusBar state is FULLSCREEN_USER_SWITCHER.
-     */
-    public boolean isFullScreenUserSwitcherState() {
-        return mState == StatusBarState.FULLSCREEN_USER_SWITCHER;
-    }
-
     boolean updateIsKeyguard() {
         return updateIsKeyguard(false /* forceStateChange */);
     }
@@ -2983,6 +2979,7 @@
     }
 
     public void showKeyguardImpl() {
+        Trace.beginSection("StatusBar#showKeyguard");
         mIsKeyguard = true;
         // In case we're locking while a smartspace transition is in progress, reset it.
         mKeyguardUnlockAnimationController.resetSmartspaceTransition();
@@ -2991,20 +2988,17 @@
             onLaunchTransitionFadingEnded();
         }
         mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
-        if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
-            mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
-        } else if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) {
+        if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) {
             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
         }
         updatePanelExpansionForKeyguard();
+        Trace.endSection();
     }
 
     private void updatePanelExpansionForKeyguard() {
         if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
                 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
             mShadeController.instantExpandNotificationsPanel();
-        } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
-            instantCollapseNotificationPanel();
         }
     }
 
@@ -4144,6 +4138,10 @@
         return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming();
     }
 
+    public boolean isBouncerShowingOverDream() {
+        return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive();
+    }
+
     /**
      * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
      */
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 316e682..d42a423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -48,9 +49,9 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.DejankUtils;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -111,6 +112,7 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
     private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+    private final DreamOverlayStateController mDreamOverlayStateController;
     private KeyguardMessageAreaController mKeyguardMessageAreaController;
     private final Lazy<ShadeController> mShadeController;
     private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -211,6 +213,7 @@
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final DockManager mDockManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateManager;
+    private final LatencyTracker mLatencyTracker;
     private KeyguardBypassController mBypassController;
     @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
 
@@ -235,6 +238,7 @@
             SysuiStatusBarStateController sysuiStatusBarStateController,
             ConfigurationController configurationController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DreamOverlayStateController dreamOverlayStateController,
             NavigationModeController navigationModeController,
             DockManager dockManager,
             NotificationShadeWindowController notificationShadeWindowController,
@@ -242,13 +246,15 @@
             NotificationMediaManager notificationMediaManager,
             KeyguardBouncer.Factory keyguardBouncerFactory,
             KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
-            Lazy<ShadeController> shadeController) {
+            Lazy<ShadeController> shadeController,
+            LatencyTracker latencyTracker) {
         mContext = context;
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
         mConfigurationController = configurationController;
         mNavigationModeController = navigationModeController;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mDreamOverlayStateController = dreamOverlayStateController;
         mKeyguardStateController = keyguardStateController;
         mMediaManager = notificationMediaManager;
         mKeyguardUpdateManager = keyguardUpdateMonitor;
@@ -257,6 +263,7 @@
         mKeyguardBouncerFactory = keyguardBouncerFactory;
         mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
         mShadeController = shadeController;
+        mLatencyTracker = latencyTracker;
     }
 
     @Override
@@ -370,6 +377,7 @@
      */
     @Override
     public void show(Bundle options) {
+        Trace.beginSection("StatusBarKeyguardViewManager#show");
         mShowing = true;
         mNotificationShadeWindowController.setKeyguardShowing(true);
         mKeyguardStateController.notifyKeyguardState(mShowing,
@@ -377,6 +385,7 @@
         reset(true /* hideBouncerWhenShowing */);
         SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                 SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
+        Trace.endSection();
     }
 
     /**
@@ -711,6 +720,7 @@
 
     @Override
     public void hide(long startTime, long fadeoutDuration) {
+        Trace.beginSection("StatusBarKeyguardViewManager#hide");
         mShowing = false;
         mKeyguardStateController.notifyKeyguardState(mShowing,
                 mKeyguardStateController.isOccluded());
@@ -810,6 +820,7 @@
         }
         SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                 SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
+        Trace.endSection();
     }
 
     private boolean needsBypassFading() {
@@ -850,15 +861,11 @@
     }
 
     private void wakeAndUnlockDejank() {
-        if (mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
-                && LatencyTracker.isEnabled(mContext)) {
+        if (mBiometricUnlockController.isWakeAndUnlock() && mLatencyTracker.isEnabled()) {
             BiometricSourceType type = mBiometricUnlockController.getBiometricType();
-            DejankUtils.postAfterTraversal(() -> {
-                    LatencyTracker.getInstance(mContext).onActionEnd(
-                            type == BiometricSourceType.FACE
-                                    ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK
-                                    : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
-            });
+            mLatencyTracker.onActionEnd(type == BiometricSourceType.FACE
+                            ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK
+                            : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
         }
     }
 
@@ -1174,8 +1181,9 @@
     }
 
     public boolean bouncerNeedsScrimming() {
-        return mOccluded || mBouncer.willDismissWithAction()
-                || mStatusBar.isFullScreenUserSwitcherState()
+        // When a dream overlay is active, scrimming will cause any expansion to immediately expand.
+        return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
+                || mBouncer.willDismissWithAction()
                 || (mBouncer.isShowing() && mBouncer.isScrimmed())
                 || mBouncer.isFullscreenBouncer();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 6d033477..79c0984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -18,11 +18,13 @@
 import android.view.View
 import android.view.WindowManager
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.AlphaProvider
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider
 import com.android.systemui.unfold.SysUIUnfoldScope
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
 import javax.inject.Inject
+import kotlin.math.max
 
 @SysUIUnfoldScope
 class StatusBarMoveFromCenterAnimationController @Inject constructor(
@@ -31,8 +33,11 @@
 ) {
 
     private val transitionListener = TransitionListener()
-    private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
-        viewCenterProvider = StatusBarViewsCenterProvider())
+    private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(
+        windowManager,
+        viewCenterProvider = StatusBarViewsCenterProvider(),
+        alphaProvider = StatusBarIconsAlphaProvider()
+    )
 
     fun onViewsReady(viewsToAnimate: Array<View>) {
         moveFromCenterAnimator.updateDisplayProperties()
@@ -65,4 +70,15 @@
             moveFromCenterAnimator.onTransitionProgress(1f)
         }
     }
+
+    private class StatusBarIconsAlphaProvider : AlphaProvider {
+        override fun getAlpha(progress: Float): Float {
+            return max(
+                0f,
+                (progress - ICONS_START_APPEARING_PROGRESS) / (1 - ICONS_START_APPEARING_PROGRESS)
+            )
+        }
+    }
 }
+
+private const val ICONS_START_APPEARING_PROGRESS = 0.75F
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index e3b4caa..d6fc0a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -23,6 +23,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -87,11 +89,8 @@
         this(context, theme, dismissOnDeviceLock, null);
     }
 
-    /**
-     * @param udfpsDialogManager If set, UDFPS will hide if this dialog is showing.
-     */
     public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
-            SystemUIDialogManager dialogManager) {
+            @Nullable SystemUIDialogManager dialogManager) {
         super(context, theme);
         mContext = context;
 
@@ -148,7 +147,7 @@
      * the device configuration changes, and the result will be used to resize this dialog window.
      */
     protected int getWidth() {
-        return getDefaultDialogWidth(mContext);
+        return getDefaultDialogWidth(this);
     }
 
     /**
@@ -279,36 +278,53 @@
         // We need to create the dialog first, otherwise the size will be overridden when it is
         // created.
         dialog.create();
-        dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()),
-                getDefaultDialogHeight());
+        dialog.getWindow().setLayout(getDefaultDialogWidth(dialog), getDefaultDialogHeight());
     }
 
-    private static int getDefaultDialogWidth(Context context) {
-        boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
-        if (!isOnTablet) {
-            return ViewGroup.LayoutParams.MATCH_PARENT;
-        }
-
+    private static int getDefaultDialogWidth(Dialog dialog) {
+        Context context = dialog.getContext();
         int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
         if (flagValue == -1) {
             // The width of bottom sheets (624dp).
-            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
-                    context.getResources().getDisplayMetrics()));
+            return calculateDialogWidthWithInsets(dialog, 624);
         } else if (flagValue == -2) {
             // The suggested small width for all dialogs (348dp)
-            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
-                    context.getResources().getDisplayMetrics()));
+            return calculateDialogWidthWithInsets(dialog, 348);
         } else if (flagValue > 0) {
             // Any given width.
-            return Math.round(
-                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
-                            context.getResources().getDisplayMetrics()));
+            return calculateDialogWidthWithInsets(dialog, flagValue);
         } else {
-            // By default we use the same width as the notification shade in portrait mode (504dp).
-            return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+            // By default we use the same width as the notification shade in portrait mode.
+            int width = context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+            if (width > 0) {
+                // If we are neither WRAP_CONTENT or MATCH_PARENT, add the background insets so that
+                // the dialog is the desired width.
+                width += getHorizontalInsets(dialog);
+            }
+            return width;
         }
     }
 
+    /**
+     * Return the pixel width {@param dialog} should be so that it is {@param widthInDp} wide,
+     * taking its background insets into consideration.
+     */
+    private static int calculateDialogWidthWithInsets(Dialog dialog, int widthInDp) {
+        float widthInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, widthInDp,
+                dialog.getContext().getResources().getDisplayMetrics());
+        return Math.round(widthInPixels + getHorizontalInsets(dialog));
+    }
+
+    private static int getHorizontalInsets(Dialog dialog) {
+        if (dialog.getWindow().getDecorView() == null) {
+            return 0;
+        }
+
+        Drawable background = dialog.getWindow().getDecorView().getBackground();
+        Insets insets = background != null ? background.getOpticalInsets() : Insets.NONE;
+        return insets.left + insets.right;
+    }
+
     private static int getDefaultDialogHeight() {
         return ViewGroup.LayoutParams.WRAP_CONTENT;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index cc65ca02..0abe8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -136,6 +136,12 @@
                 globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
     }
 
+    override fun shouldDelayKeyguardShow(): Boolean =
+        shouldPlayAnimation()
+
+    override fun isKeyguardShowDelayed(): Boolean =
+        isAnimationPlaying()
+
     /**
      * Animates in the provided keyguard view, ending in the same position that it will be in on
      * AOD.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index f5364b9..d3ff4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -40,6 +40,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -231,7 +232,8 @@
             ActivityLaunchAnimator activityLaunchAnimator,
             NotifPipelineFlags notifPipelineFlags,
             InteractionJankMonitor jankMonitor,
-            DeviceStateManager deviceStateManager) {
+            DeviceStateManager deviceStateManager,
+            DreamOverlayStateController dreamOverlayStateController) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -327,7 +329,8 @@
                 activityLaunchAnimator,
                 notifPipelineFlags,
                 jankMonitor,
-                deviceStateManager
+                deviceStateManager,
+                dreamOverlayStateController
         );
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 48949f92..3205e09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -118,6 +118,7 @@
     private boolean mColorized;
     private int mTint;
     private boolean mResetting;
+    private boolean mWasSpinning;
 
     // TODO(b/193539698): move these to a Controller
     private RemoteInputController mController;
@@ -439,6 +440,10 @@
                 mEditText.requestFocus();
             }
         }
+        if (mWasSpinning) {
+            mController.addSpinning(mEntry.getKey(), mToken);
+            mWasSpinning = false;
+        }
     }
 
     @Override
@@ -447,6 +452,7 @@
         mEditText.removeTextChangedListener(mTextWatcher);
         mEditText.setOnEditorActionListener(null);
         mEditText.mRemoteInputView = null;
+        mWasSpinning = mController.isSpinning(mEntry.getKey(), mToken);
         if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
             return;
         }
@@ -533,6 +539,8 @@
         if (isActive() && mWrapper != null) {
             mWrapper.setRemoteInputVisible(true);
         }
+
+        mWasSpinning = false;
     }
 
     private void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 9f20bc5..1b73595 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -19,7 +19,6 @@
 import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -427,22 +426,6 @@
         return mSimpleUserSwitcher;
     }
 
-    public boolean useFullscreenUserSwitcher() {
-        // Use adb to override:
-        // adb shell settings put system enable_fullscreen_user_switcher 0  # Turn it off.
-        // adb shell settings put system enable_fullscreen_user_switcher 1  # Turn it on.
-        // Restart SystemUI or adb reboot.
-        final int DEFAULT = -1;
-        final int overrideUseFullscreenUserSwitcher =
-                whitelistIpcs(() -> Settings.System.getInt(mContext.getContentResolver(),
-                        "enable_fullscreen_user_switcher", DEFAULT));
-        if (overrideUseFullscreenUserSwitcher != DEFAULT) {
-            return overrideUseFullscreenUserSwitcher != 0;
-        }
-        // Otherwise default to the build setting.
-        return mContext.getResources().getBoolean(R.bool.config_enableFullscreenUserSwitcher);
-    }
-
     public void setResumeUserOnGuestLogout(boolean resume) {
         mResumeUserOnGuestLogout = resume;
     }
@@ -455,6 +438,13 @@
         }
     }
 
+    /**
+     * Returns whether the current user is a system user.
+     */
+    public boolean isSystemUser() {
+        return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
+    }
+
     public void removeUserId(int userId) {
         if (userId == UserHandle.USER_SYSTEM) {
             Log.w(TAG, "User " + userId + " could not removed.");
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index c481fc9..2e627a8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -20,7 +20,6 @@
 import android.os.PowerManager
 import android.provider.Settings
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.phone.ScreenOffAnimation
@@ -28,7 +27,6 @@
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
 import com.android.systemui.util.settings.GlobalSettings
-import dagger.Lazy
 import javax.inject.Inject
 
 /**
@@ -40,7 +38,6 @@
 @Inject
 constructor(
     @Main private val handler: Handler,
-    private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
     private val globalSettings: GlobalSettings
 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
@@ -57,7 +54,6 @@
         statusBar.notificationPanelViewController.startFoldToAodAnimation {
             // End action
             isAnimationPlaying = false
-            keyguardViewMediatorLazy.get().maybeHandlePendingLock()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 07f9c54..7350b37 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -18,6 +18,7 @@
 
 import com.android.keyguard.KeyguardUnfoldTransition
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.NotificationPanelUnfoldAnimationController
 import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -85,6 +86,8 @@
 
     fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController
 
+    fun getNotificationPanelUnfoldAnimationController(): NotificationPanelUnfoldAnimationController
+
     fun getFoldAodAnimationController(): FoldAodAnimationController
 
     fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
new file mode 100644
index 0000000..7687432
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.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 com.android.systemui.util.service;
+
+/**
+ * The {@link Observer} interface specifies an entity which listeners
+ * can be informed of changes to the source, which will require updating. Note that this deals
+ * with changes to the source itself, not content which will be updated through the interface.
+ */
+public interface Observer {
+    /**
+     * Callback for receiving updates from the {@link Observer}.
+     */
+    interface Callback {
+        /**
+         * Invoked when the source has changed.
+         */
+        void onSourceChanged();
+    }
+
+    /**
+     * Adds a callback to receive future updates from the {@link Observer}.
+     */
+    void addCallback(Callback callback);
+
+    /**
+     * Removes a callback from receiving further updates.
+     * @param callback
+     */
+    void removeCallback(Callback callback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java
new file mode 100644
index 0000000..2ee7b20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.util.Log;
+
+import com.android.systemui.communal.CommunalSource;
+
+import com.google.android.collect.Lists;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.inject.Inject;
+
+/**
+ * {@link PackageObserver} allows for monitoring the system for changes relating to a particular
+ * package. This can be used by {@link CommunalSource} clients to detect when a related package
+ * has changed and reloading is necessary.
+ */
+public class PackageObserver implements Observer {
+    private static final String TAG = "PackageObserver";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Log.d(TAG, "package added receiver - onReceive");
+            }
+
+            final Iterator<WeakReference<Callback>> iter = mCallbacks.iterator();
+            while (iter.hasNext()) {
+                final Callback callback = iter.next().get();
+                if (callback != null) {
+                    callback.onSourceChanged();
+                } else {
+                    iter.remove();
+                }
+            }
+        }
+    };
+
+    private final String mPackageName;
+    private final Context mContext;
+
+    @Inject
+    public PackageObserver(Context context, ComponentName component) {
+        mContext = context;
+        mPackageName = component.getPackageName();
+    }
+
+    @Override
+    public void addCallback(Callback callback) {
+        if (DEBUG) {
+            Log.d(TAG, "addCallback:" + callback);
+        }
+        mCallbacks.add(new WeakReference<>(callback));
+
+        // Only register for listening to package additions on first callback.
+        if (mCallbacks.size() > 1) {
+            return;
+        }
+
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(mPackageName, PatternMatcher.PATTERN_LITERAL);
+        // Note that we directly register the receiver here as data schemes are not supported by
+        // BroadcastDispatcher.
+        mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    public void removeCallback(Callback callback) {
+        if (DEBUG) {
+            Log.d(TAG, "removeCallback:" + callback);
+        }
+        final boolean removed = mCallbacks.removeIf(el -> el.get() == callback);
+
+        if (removed && mCallbacks.isEmpty()) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
new file mode 100644
index 0000000..292c062
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.service;
+
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION;
+
+import android.util.Log;
+
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.SystemClock;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
+ * {@link ObservableServiceConnection}.
+ * @param <T> The transformed connection type handled by the service.
+ */
+public class PersistentConnectionManager<T> {
+    private static final String TAG = "PersistentConnManager";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final SystemClock mSystemClock;
+    private final DelayableExecutor mMainExecutor;
+    private final int mBaseReconnectDelayMs;
+    private final int mMaxReconnectAttempts;
+    private final int mMinConnectionDuration;
+    private final Observer mObserver;
+
+    private int mReconnectAttempts = 0;
+    private Runnable mCurrentReconnectCancelable;
+
+    private final ObservableServiceConnection<T> mConnection;
+
+    private final Runnable mConnectRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mCurrentReconnectCancelable = null;
+            mConnection.bind();
+        }
+    };
+
+    private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
+
+    private final ObservableServiceConnection.Callback mConnectionCallback =
+            new ObservableServiceConnection.Callback() {
+        private long mStartTime;
+
+        @Override
+        public void onConnected(ObservableServiceConnection connection, Object proxy) {
+            mStartTime = mSystemClock.currentTimeMillis();
+        }
+
+        @Override
+        public void onDisconnected(ObservableServiceConnection connection, int reason) {
+            if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
+                initiateConnectionAttempt();
+            } else {
+                scheduleConnectionAttempt();
+            }
+        }
+    };
+
+    @Inject
+    public PersistentConnectionManager(
+            SystemClock clock,
+            DelayableExecutor mainExecutor,
+            @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
+            @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
+            @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
+            @Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
+            @Named(OBSERVER) Observer observer) {
+        mSystemClock = clock;
+        mMainExecutor = mainExecutor;
+        mConnection = serviceConnection;
+        mObserver = observer;
+
+        mMaxReconnectAttempts = maxReconnectAttempts;
+        mBaseReconnectDelayMs = baseReconnectDelayMs;
+        mMinConnectionDuration = minConnectionDurationMs;
+    }
+
+    /**
+     * Begins the {@link PersistentConnectionManager} by connecting to the associated service.
+     */
+    public void start() {
+        mConnection.addCallback(mConnectionCallback);
+        mObserver.addCallback(mObserverCallback);
+        initiateConnectionAttempt();
+    }
+
+    /**
+     * Brings down the {@link PersistentConnectionManager}, disconnecting from the service.
+     */
+    public void stop() {
+        mConnection.removeCallback(mConnectionCallback);
+        mObserver.removeCallback(mObserverCallback);
+        mConnection.unbind();
+    }
+
+    private void initiateConnectionAttempt() {
+        // Reset attempts
+        mReconnectAttempts = 0;
+
+        // The first attempt is always a direct invocation rather than delayed.
+        mConnection.bind();
+    }
+
+    private void scheduleConnectionAttempt() {
+        // always clear cancelable if present.
+        if (mCurrentReconnectCancelable != null) {
+            mCurrentReconnectCancelable.run();
+            mCurrentReconnectCancelable = null;
+        }
+
+        if (mReconnectAttempts >= mMaxReconnectAttempts) {
+            if (DEBUG) {
+                Log.d(TAG, "exceeded max connection attempts.");
+            }
+            return;
+        }
+
+        final long reconnectDelayMs =
+                (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
+
+        if (DEBUG) {
+            Log.d(TAG,
+                    "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
+        }
+
+        mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+                reconnectDelayMs);
+
+        mReconnectAttempts++;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
new file mode 100644
index 0000000..c62c957
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.systemui.util.service.dagger;
+
+import android.content.res.Resources;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module containing components and parameters for
+ * {@link com.android.systemui.util.service.ObservableServiceConnection}
+ * and {@link com.android.systemui.util.service.PersistentConnectionManager}.
+ */
+@Module(subcomponents = {
+        PackageObserverComponent.class,
+})
+public class ObservableServiceModule {
+    public static final String MAX_RECONNECT_ATTEMPTS = "max_reconnect_attempts";
+    public static final String BASE_RECONNECT_DELAY_MS = "base_reconnect_attempts";
+    public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms";
+    public static final String SERVICE_CONNECTION = "service_connection";
+    public static final String OBSERVER = "observer";
+
+    @Provides
+    @Named(MAX_RECONNECT_ATTEMPTS)
+    static int providesMaxReconnectAttempts(@Main Resources resources) {
+        return resources.getInteger(
+                R.integer.config_communalSourceMaxReconnectAttempts);
+    }
+
+    @Provides
+    @Named(BASE_RECONNECT_DELAY_MS)
+    static int provideBaseReconnectDelayMs(@Main Resources resources) {
+        return resources.getInteger(
+                R.integer.config_communalSourceReconnectBaseDelay);
+    }
+
+    @Provides
+    @Named(MIN_CONNECTION_DURATION_MS)
+    static int providesMinConnectionDuration(@Main Resources resources) {
+        return resources.getInteger(
+                R.integer.config_connectionMinDuration);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java
new file mode 100644
index 0000000..8ee39b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.service.dagger;
+
+import android.content.ComponentName;
+
+import com.android.systemui.util.service.PackageObserver;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Generates a scoped {@link PackageObserver}.
+ */
+@Subcomponent
+public interface PackageObserverComponent {
+    /**
+     * Generates a {@link PackageObserverComponent} instance.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        PackageObserverComponent create(@BindsInstance ComponentName component);
+    }
+
+    /**
+     * Creates a {@link PackageObserver}.
+     */
+    PackageObserver getPackageObserver();
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 164f83d..6c1f008 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
@@ -20,23 +20,21 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUnfoldTransition.Companion.LEFT
-import com.android.keyguard.KeyguardUnfoldTransition.Companion.RIGHT
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import com.android.systemui.util.mockito.capture
-import org.junit.Assert.assertEquals
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
 
 /**
  * Translates items away/towards the hinge when the device is opened/closed. This is controlled by
@@ -46,14 +44,11 @@
 @RunWith(AndroidTestingRunner::class)
 class KeyguardUnfoldTransitionTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+    @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
 
-    @Captor
-    private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+    @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
 
-    @Mock
-    private lateinit var parent: ViewGroup
+    @Mock private lateinit var parent: ViewGroup
 
     private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition
     private lateinit var progressListener: TransitionProgressListener
@@ -63,87 +58,35 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        xTranslationMax = context.resources.getDimensionPixelSize(
-            R.dimen.keyguard_unfold_translation_x).toFloat()
+        xTranslationMax =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
 
-        keyguardUnfoldTransition = KeyguardUnfoldTransition(
-            getContext(),
-            progressProvider
-        )
-
-        verify(progressProvider).addCallback(capture(progressListenerCaptor))
-        progressListener = progressListenerCaptor.value
+        keyguardUnfoldTransition = KeyguardUnfoldTransition(context, progressProvider)
 
         keyguardUnfoldTransition.setup(parent)
         keyguardUnfoldTransition.statusViewCentered = false
-    }
 
-    @Test
-    fun onTransition_noMatchingIds() {
-        // GIVEN no views matching any ids
-        // WHEN the transition starts
-        progressListener.onTransitionStarted()
-        progressListener.onTransitionProgress(.1f)
-
-        // THEN nothing... no exceptions
-    }
-
-    @Test
-    fun onTransition_oneMovesLeft() {
-        // GIVEN one view with a matching id
-        val view = View(getContext())
-        `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(view)
-
-        moveAndValidate(listOf(view to LEFT))
-    }
-
-    @Test
-    fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
-        // GIVEN two views with a matching id
-        val leftView = View(getContext())
-        val rightView = View(getContext())
-        `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(leftView)
-        `when`(parent.findViewById<View>(R.id.notification_stack_scroller)).thenReturn(rightView)
-
-        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
-        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+        verify(progressProvider).addCallback(capture(progressListenerCaptor))
+        progressListener = progressListenerCaptor.value
     }
 
     @Test
     fun onTransition_centeredViewDoesNotMove() {
         keyguardUnfoldTransition.statusViewCentered = true
 
-        val view = View(getContext())
+        val view = View(context)
         `when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
 
-        moveAndValidate(listOf(view to 0))
-    }
-
-    private fun moveAndValidate(list: List<Pair<View, Int>>) {
-        // Compare values as ints because -0f != 0f
-
-        // WHEN the transition starts
         progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
         progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isZero()
 
-        list.forEach { (view, direction) ->
-            assertEquals((-xTranslationMax * direction).toInt(), view.getTranslationX().toInt())
-        }
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isZero()
 
-        // WHEN the transition progresses, translation is updated
-        progressListener.onTransitionProgress(.5f)
-        list.forEach { (view, direction) ->
-            assertEquals(
-                (-xTranslationMax / 2f * direction).toInt(),
-                view.getTranslationX().toInt()
-            )
-        }
-
-        // WHEN the transition ends, translation is completed
-        progressListener.onTransitionProgress(1f)
         progressListener.onTransitionFinished()
-        list.forEach { (view, _) ->
-            assertEquals(0, view.getTranslationX().toInt())
-        }
+        assertThat(view.translationX).isZero()
     }
 }
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 5d39eef..c37e966 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -27,10 +27,12 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -46,6 +48,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorProperties;
@@ -70,6 +73,7 @@
 import com.android.internal.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.concurrency.FakeExecution;
@@ -80,6 +84,7 @@
 import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -99,6 +104,8 @@
     @Mock
     private IBiometricSysuiReceiver mReceiver;
     @Mock
+    private IBiometricContextListener mContextListener;
+    @Mock
     private AuthDialog mDialog1;
     @Mock
     private AuthDialog mDialog2;
@@ -120,10 +127,14 @@
     private DisplayManager mDisplayManager;
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
     @Captor
     ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
     @Captor
     ArgumentCaptor<FingerprintStateListener> mFingerprintStateCaptor;
+    @Captor
+    ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
 
     private TestableContext mContextSpy;
     private Execution mExecution;
@@ -175,12 +186,15 @@
 
         mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
                 mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
-                () -> mUdfpsController, () -> mSidefpsController);
+                () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController);
 
         mAuthController.start();
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
                 mAuthenticatorsRegisteredCaptor.capture());
 
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
+
         mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
 
         // Ensures that the operations posted on the handler get executed.
@@ -198,7 +212,8 @@
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                 mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
-                mFaceManager, () -> mUdfpsController, () -> mSidefpsController);
+                mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+                mStatusBarStateController);
         authController.start();
 
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -221,7 +236,8 @@
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                 mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
-                mFaceManager, () -> mUdfpsController, () -> mSidefpsController);
+                mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+                mStatusBarStateController);
         authController.start();
 
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -656,6 +672,19 @@
         verify(callback).onBiometricPromptDismissed();
     }
 
+    @Test
+    public void testForwardsDozeEvent() throws RemoteException {
+        mAuthController.setBiometicContextListener(mContextListener);
+
+        mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
+        mStatusBarStateListenerCaptor.getValue().onDozingChanged(true);
+
+        InOrder order = inOrder(mContextListener);
+        // invoked twice since the initial state is false
+        order.verify(mContextListener, times(2)).onDozeChanged(eq(false));
+        order.verify(mContextListener).onDozeChanged(eq(true));
+    }
+
     // Helpers
 
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
@@ -705,10 +734,12 @@
                 FingerprintManager fingerprintManager,
                 FaceManager faceManager,
                 Provider<UdfpsController> udfpsControllerFactory,
-                Provider<SidefpsController> sidefpsControllerFactory) {
+                Provider<SidefpsController> sidefpsControllerFactory,
+                StatusBarStateController statusBarStateController) {
             super(context, execution, commandQueue, activityTaskManager, windowManager,
                     fingerprintManager, faceManager, udfpsControllerFactory,
-                    sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, mHandler);
+                    sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
+                    statusBarStateController, mHandler);
         }
 
         @Override
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 b3b5fa5..d5bd67a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -91,6 +91,9 @@
     @Mock
     DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
 
+    @Mock
+    DreamOverlayStateController mStateController;
+
 
     DreamOverlayService mService;
 
@@ -115,6 +118,7 @@
 
         mService = new DreamOverlayService(mContext, mMainExecutor,
                 mDreamOverlayComponentFactory,
+                mStateController,
                 mKeyguardUpdateMonitor);
         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 7d0833d..627da3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -16,11 +16,15 @@
 
 package com.android.systemui.dreams;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.clearInvocations;
+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.testing.AndroidTestingRunner;
 
@@ -35,6 +39,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collection;
@@ -56,7 +61,29 @@
     }
 
     @Test
-    public void testCallback() throws Exception {
+    public void testStateChange() {
+        final DreamOverlayStateController stateController = new DreamOverlayStateController(
+                mExecutor);
+        stateController.addCallback(mCallback);
+        stateController.setOverlayActive(true);
+        mExecutor.runAllReady();
+
+        verify(mCallback).onStateChanged();
+        assertThat(stateController.isOverlayActive()).isTrue();
+
+        Mockito.clearInvocations(mCallback);
+        stateController.setOverlayActive(true);
+        mExecutor.runAllReady();
+        verify(mCallback, never()).onStateChanged();
+
+        stateController.setOverlayActive(false);
+        mExecutor.runAllReady();
+        verify(mCallback).onStateChanged();
+        assertThat(stateController.isOverlayActive()).isFalse();
+    }
+
+    @Test
+    public void testCallback() {
         final DreamOverlayStateController stateController = new DreamOverlayStateController(
                 mExecutor);
         stateController.addCallback(mCallback);
@@ -94,4 +121,43 @@
         mExecutor.runAllReady();
         verify(mCallback, times(1)).onComplicationsChanged();
     }
+
+    @Test
+    public void testComplicationFiltering() {
+        final DreamOverlayStateController stateController =
+                new DreamOverlayStateController(mExecutor);
+
+        final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
+        final Complication weatherComplication = Mockito.mock(Complication.class);
+        when(alwaysAvailableComplication.getRequiredTypeAvailability())
+                .thenReturn(Complication.COMPLICATION_TYPE_NONE);
+        when(weatherComplication.getRequiredTypeAvailability())
+                .thenReturn(Complication.COMPLICATION_TYPE_WEATHER);
+
+        stateController.addComplication(alwaysAvailableComplication);
+        stateController.addComplication(weatherComplication);
+
+        final DreamOverlayStateController.Callback callback =
+                Mockito.mock(DreamOverlayStateController.Callback.class);
+
+        stateController.addCallback(callback);
+        mExecutor.runAllReady();
+
+        {
+            final Collection<Complication> complications = stateController.getComplications();
+            assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
+            assertThat(complications.contains(weatherComplication)).isFalse();
+        }
+
+        stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_WEATHER);
+        mExecutor.runAllReady();
+        verify(callback).onAvailableComplicationTypesChanged();
+
+        {
+            final Collection<Complication> complications = stateController.getComplications();
+            assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
+            assertThat(complications.contains(weatherComplication)).isTrue();
+        }
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
new file mode 100644
index 0000000..3b17a80
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SmartSpaceComplicationTest extends SysuiTestCase {
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private LockscreenSmartspaceController mSmartspaceController;
+
+    @Mock
+    private DreamOverlayStateController mDreamOverlayStateController;
+
+    @Mock
+    private SmartSpaceComplication mComplication;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Ensures {@link SmartSpaceComplication} is only registered when it is available.
+     */
+    @Test
+    public void testAvailability() {
+        when(mSmartspaceController.isEnabled()).thenReturn(false);
+
+        final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant(
+                mContext,
+                mDreamOverlayStateController,
+                mComplication,
+                mSmartspaceController);
+        registrant.start();
+        verify(mDreamOverlayStateController, never()).addComplication(any());
+
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        registrant.start();
+        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
index feeea5d..5fcf414 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
@@ -75,6 +75,10 @@
             callbackCaptor.getValue().onComplicationsChanged();
 
             verifyUpdate(observer, complications);
+
+            callbackCaptor.getValue().onAvailableComplicationTypesChanged();
+
+            verifyUpdate(observer, complications);
         });
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index f227a9b..d5ab708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -27,6 +27,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -122,6 +123,34 @@
     }
 
     /**
+     * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
+     */
+    @Test
+    public void testSnapToGuide() {
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0,
+                        true),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+        addComplication(engine, firstViewInfo);
+
+        // Ensure the view is added to the top end corner
+        verifyChange(firstViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.startToEnd == R.id.complication_end_guide).isTrue();
+        });
+    }
+
+    /**
      * Ensures layout in a particular direction updates.
      */
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
new file mode 100644
index 0000000..f1978b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
+import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationUtilsTest extends SysuiTestCase {
+    @Test
+    public void testConvertComplicationType() {
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME))
+                .isEqualTo(COMPLICATION_TYPE_TIME);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE))
+                .isEqualTo(COMPLICATION_TYPE_DATE);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_WEATHER))
+                .isEqualTo(COMPLICATION_TYPE_WEATHER);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_AIR_QUALITY))
+                .isEqualTo(COMPLICATION_TYPE_AIR_QUALITY);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_CAST_INFO))
+                .isEqualTo(COMPLICATION_TYPE_CAST_INFO);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
new file mode 100644
index 0000000..b02c506
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamClockDateComplicationTest extends SysuiTestCase {
+    @SuppressWarnings("HidingField")
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private DreamOverlayStateController mDreamOverlayStateController;
+
+    @Mock
+    private DreamClockDateComplication mComplication;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Ensures {@link DreamClockDateComplication} is registered.
+     */
+    @Test
+    public void testComplicationAdded() {
+        final DreamClockDateComplication.Registrant registrant =
+                new DreamClockDateComplication.Registrant(
+                        mContext,
+                        mDreamOverlayStateController,
+                        mComplication);
+        registrant.start();
+        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
new file mode 100644
index 0000000..088b4d5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamClockTimeComplicationTest extends SysuiTestCase {
+    @SuppressWarnings("HidingField")
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private DreamOverlayStateController mDreamOverlayStateController;
+
+    @Mock
+    private DreamClockTimeComplication mComplication;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Ensures {@link DreamClockTimeComplication} is registered.
+     */
+    @Test
+    public void testComplicationAdded() {
+        final DreamClockTimeComplication.Registrant registrant =
+                new DreamClockTimeComplication.Registrant(
+                        mContext,
+                        mDreamOverlayStateController,
+                        mComplication);
+        registrant.start();
+        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
new file mode 100644
index 0000000..151742a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamWeatherComplicationTest extends SysuiTestCase {
+    @SuppressWarnings("HidingField")
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private LockscreenSmartspaceController mSmartspaceController;
+
+    @Mock
+    private DreamOverlayStateController mDreamOverlayStateController;
+
+    @Mock
+    private DreamWeatherComplication mComplication;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Ensures {@link DreamWeatherComplication} is only registered when it is available.
+     */
+    @Test
+    public void testComplicationAvailability() {
+        when(mSmartspaceController.isEnabled()).thenReturn(false);
+        final DreamWeatherComplication.Registrant registrant =
+                new DreamWeatherComplication.Registrant(
+                        mContext,
+                        mSmartspaceController,
+                        mDreamOverlayStateController,
+                        mComplication);
+        registrant.start();
+        verify(mDreamOverlayStateController, never()).addComplication(any());
+
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        registrant.start();
+        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..1a8326f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.testing.AndroidTestingRunner;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+    @Mock
+    StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+
+    @Mock
+    StatusBar mStatusBar;
+
+    @Mock
+    NotificationShadeWindowController mNotificationShadeWindowController;
+
+    @Mock
+    FlingAnimationUtils mFlingAnimationUtils;
+
+
+    @Mock
+    FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+    @Mock
+    DreamTouchHandler.TouchSession mTouchSession;
+
+    BouncerSwipeTouchHandler mTouchHandler;
+
+    @Mock
+    BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+    @Mock
+    ValueAnimator mValueAnimator;
+
+    @Mock
+    BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+    @Mock
+    VelocityTracker mVelocityTracker;
+
+    private static final float TOUCH_REGION = .3f;
+    private static final float SCREEN_HEIGHT_PX = 100;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTouchHandler = new BouncerSwipeTouchHandler(
+                mStatusBarKeyguardViewManager,
+                mStatusBar,
+                mNotificationShadeWindowController,
+                mValueAnimatorCreator,
+                mVelocityTrackerFactory,
+                mFlingAnimationUtils,
+                mFlingAnimationUtilsClosing,
+                TOUCH_REGION);
+        when(mStatusBar.getDisplayHeight()).thenReturn(SCREEN_HEIGHT_PX);
+        when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+        when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+    }
+
+    private static void beginValidSwipe(GestureDetector.OnGestureListener listener) {
+        listener.onDown(MotionEvent.obtain(0, 0,
+                MotionEvent.ACTION_DOWN, 0,
+                SCREEN_HEIGHT_PX - (.5f * TOUCH_REGION * SCREEN_HEIGHT_PX), 0));
+    }
+
+    /**
+     * Ensures expansion only happens when touch down happens in valid part of the screen.
+     */
+    @Test
+    public void testSessionStart() {
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any());
+        ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
+                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+        verify(mTouchSession).registerInputListener(eventListenerCaptor.capture());
+
+        final Random random = new Random(System.currentTimeMillis());
+
+        // If an initial touch down meeting criteria has been met, scroll behavior should be
+        // ignored.
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        random.nextFloat(),
+                        random.nextFloat())).isFalse();
+
+        // A touch at the top of the screen should also not trigger listening.
+        final MotionEvent touchDownEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN,
+                0, 0, 0);
+
+        gestureListenerCaptor.getValue().onDown(touchDownEvent);
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        random.nextFloat(),
+                        random.nextFloat())).isFalse();
+
+        // A touch within range at the bottom of the screen should trigger listening
+        beginValidSwipe(gestureListenerCaptor.getValue());
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        random.nextFloat(),
+                        random.nextFloat())).isTrue();
+    }
+
+    /**
+     * Makes sure expansion amount is proportional to scroll.
+     */
+    @Test
+    public void testExpansionAmount() {
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+        beginValidSwipe(gestureListenerCaptor.getValue());
+
+        final float scrollAmount = .3f;
+        final float distanceY = SCREEN_HEIGHT_PX * scrollAmount;
+
+        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX, 0);
+        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX  - distanceY, 0);
+
+        assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY))
+                .isTrue();
+
+        // Ensure only called once
+        verify(mStatusBarKeyguardViewManager)
+                .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean());
+
+        // Ensure correct expansion passed in.
+        verify(mStatusBarKeyguardViewManager)
+                .onPanelExpansionChanged(eq(1 - scrollAmount), eq(false), eq(true));
+    }
+
+    private void swipeToPosition(float position, float velocityY) {
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
+                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+        verify(mTouchSession).registerInputListener(inputEventListenerCaptor.capture());
+
+        when(mVelocityTracker.getYVelocity()).thenReturn(velocityY);
+
+        beginValidSwipe(gestureListenerCaptor.getValue());
+
+        final float distanceY = SCREEN_HEIGHT_PX * position;
+
+        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX, 0);
+        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX  - distanceY, 0);
+
+        assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY))
+                .isTrue();
+
+        final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
+                0, 0, 0);
+
+        inputEventListenerCaptor.getValue().onInputEvent(upEvent);
+    }
+
+    /**
+     * Tests that ending a swipe before the set expansion threshold leads to bouncer collapsing
+     * down.
+     */
+    @Test
+    public void testCollapseOnThreshold() {
+        final float swipeUpPercentage = .3f;
+        swipeToPosition(swipeUpPercentage, -1);
+
+        verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+                eq(KeyguardBouncer.EXPANSION_VISIBLE));
+        verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), anyFloat(), anyFloat(),
+                anyFloat(), anyFloat());
+        verify(mValueAnimator).start();
+    }
+
+    /**
+     * Tests that ending a swipe above the set expansion threshold will continue the expansion.
+     */
+    @Test
+    public void testExpandOnThreshold() {
+        final float swipeUpPercentage = .7f;
+        swipeToPosition(swipeUpPercentage, 1);
+
+        verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+                eq(KeyguardBouncer.EXPANSION_HIDDEN));
+        verify(mFlingAnimationUtils).apply(eq(mValueAnimator), anyFloat(), anyFloat(),
+                anyFloat(), anyFloat());
+        verify(mValueAnimator).start();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java
new file mode 100644
index 0000000..1171bd2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class HdmiCecSetMenuLanguageHelperTest extends SysuiTestCase {
+
+    private HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
+
+    @Mock
+    private Executor mExecutor;
+
+    @Mock
+    private SecureSettings mSecureSettings;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mSecureSettings.getString(
+                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST)).thenReturn(null);
+        mHdmiCecSetMenuLanguageHelper =
+                new HdmiCecSetMenuLanguageHelper(mExecutor, mSecureSettings);
+    }
+
+    @Test
+    public void testSetGetLocale() {
+        mHdmiCecSetMenuLanguageHelper.setLocale("en");
+        assertThat(mHdmiCecSetMenuLanguageHelper.getLocale()).isEqualTo(Locale.ENGLISH);
+    }
+
+    @Test
+    public void testIsLocaleDenylisted_EmptyByDefault() {
+        mHdmiCecSetMenuLanguageHelper.setLocale("en");
+        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
+    }
+
+    @Test
+    public void testIsLocaleDenylisted_AcceptLanguage() {
+        mHdmiCecSetMenuLanguageHelper.setLocale("de");
+        mHdmiCecSetMenuLanguageHelper.acceptLocale();
+        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
+        verify(mExecutor).execute(any());
+    }
+
+    @Test
+    public void testIsLocaleDenylisted_DeclineLanguage() {
+        mHdmiCecSetMenuLanguageHelper.setLocale("de");
+        mHdmiCecSetMenuLanguageHelper.declineLocale();
+        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+        verify(mSecureSettings).putString(
+                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de");
+    }
+
+    @Test
+    public void testIsLocaleDenylisted_DeclineTwoLanguages() {
+        mHdmiCecSetMenuLanguageHelper.setLocale("de");
+        mHdmiCecSetMenuLanguageHelper.declineLocale();
+        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+        verify(mSecureSettings).putString(
+                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de");
+        mHdmiCecSetMenuLanguageHelper.setLocale("pl");
+        mHdmiCecSetMenuLanguageHelper.declineLocale();
+        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+        verify(mSecureSettings).putString(
+                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de,pl");
+    }
+}
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 da8ab27..d94e2ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -98,6 +99,7 @@
     private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private @Mock ScreenOnCoordinator mScreenOnCoordinator;
     private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
+    private @Mock DreamOverlayStateController mDreamOverlayStateController;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -202,6 +204,7 @@
                 () -> mNotificationShadeDepthController,
                 mScreenOnCoordinator,
                 mInteractionJankMonitor,
+                mDreamOverlayStateController,
                 mNotificationShadeWindowControllerLazy);
         mViewMediator.start();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
index 43d9a75..dc7026d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -95,7 +95,6 @@
     fun testVisibleOnKeyguardOrFullScreenUserSwitcher() {
         testStateVisibility(StatusBarState.SHADE, GONE)
         testStateVisibility(StatusBarState.SHADE_LOCKED, GONE)
-        testStateVisibility(StatusBarState.FULLSCREEN_USER_SWITCHER, VISIBLE)
         testStateVisibility(StatusBarState.KEYGUARD, VISIBLE)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 140a395..609291a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -125,8 +125,9 @@
     private lateinit var settings: View
     private lateinit var settingsText: TextView
     private lateinit var cancel: View
+    private lateinit var cancelText: TextView
     private lateinit var dismiss: FrameLayout
-    private lateinit var dismissLabel: View
+    private lateinit var dismissText: TextView
 
     private lateinit var session: MediaSession
     private val device = MediaDeviceData(true, null, DEVICE_NAME)
@@ -163,8 +164,9 @@
         settings = View(context)
         settingsText = TextView(context)
         cancel = View(context)
+        cancelText = TextView(context)
         dismiss = FrameLayout(context)
-        dismissLabel = View(context)
+        dismissText = TextView(context)
         initPlayerHolderMocks()
         initSessionHolderMocks()
 
@@ -244,13 +246,15 @@
         whenever(holder.settings).thenReturn(settings)
         whenever(holder.settingsText).thenReturn(settingsText)
         whenever(holder.cancel).thenReturn(cancel)
+        whenever(holder.cancelText).thenReturn(cancelText)
         whenever(holder.dismiss).thenReturn(dismiss)
-        whenever(holder.dismissLabel).thenReturn(dismissLabel)
+        whenever(holder.dismissText).thenReturn(dismissText)
     }
 
     /** Mock view holder for session player */
     private fun initSessionHolderMocks() {
         whenever(sessionHolder.player).thenReturn(view)
+        whenever(sessionHolder.albumView).thenReturn(albumView)
         whenever(sessionHolder.appIcon).thenReturn(appIcon)
         whenever(sessionHolder.titleText).thenReturn(titleText)
         whenever(sessionHolder.artistText).thenReturn(artistText)
@@ -284,8 +288,9 @@
         whenever(sessionHolder.settings).thenReturn(settings)
         whenever(sessionHolder.settingsText).thenReturn(settingsText)
         whenever(sessionHolder.cancel).thenReturn(cancel)
+        whenever(sessionHolder.cancelText).thenReturn(cancelText)
         whenever(sessionHolder.dismiss).thenReturn(dismiss)
-        whenever(sessionHolder.dismissLabel).thenReturn(dismissLabel)
+        whenever(sessionHolder.dismissText).thenReturn(dismissText)
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index a3ffb2f..97b3b10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
+import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -85,6 +86,8 @@
     private lateinit var configurationController: ConfigurationController
     @Mock
     private lateinit var uniqueObjectHostView: UniqueObjectHostView
+    @Mock
+    private lateinit var dreamOverlayStateController: DreamOverlayStateController
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -110,7 +113,8 @@
                 notificationLockscreenUserManager,
                 configurationController,
                 wakefulnessLifecycle,
-                statusBarKeyguardViewManager)
+                statusBarKeyguardViewManager,
+                dreamOverlayStateController)
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
         setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
new file mode 100644
index 0000000..29188da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.FrameLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.util.animation.UniqueObjectHostView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaComplicationViewControllerTest extends SysuiTestCase {
+    @Mock
+    private MediaHost mMediaHost;
+
+    @Mock
+    private UniqueObjectHostView mView;
+
+    @Mock
+    private FrameLayout mComplicationContainer;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mMediaHost.hostView = mView;
+    }
+
+    @Test
+    public void testMediaHostViewInteraction() {
+        final MediaComplicationViewController controller = new MediaComplicationViewController(
+                mComplicationContainer, mMediaHost);
+
+        controller.init();
+
+        controller.onViewAttached();
+        verify(mComplicationContainer).addView(eq(mView));
+
+        controller.onViewDetached();
+        verify(mComplicationContainer).removeView(eq(mView));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
new file mode 100644
index 0000000..114fc90
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaDreamSentinelTest extends SysuiTestCase {
+    @Mock
+    MediaDataManager mMediaDataManager;
+
+    @Mock
+    DreamOverlayStateController mDreamOverlayStateController;
+
+    @Mock
+    MediaDreamComplication mComplication;
+
+    final String mKey = "key";
+    final String mOldKey = "old_key";
+
+    @Mock
+    MediaData mData;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testComplicationAddition() {
+        final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+                mDreamOverlayStateController, mComplication);
+
+        sentinel.start();
+
+        ArgumentCaptor<MediaDataManager.Listener> listenerCaptor =
+                ArgumentCaptor.forClass(MediaDataManager.Listener.class);
+        verify(mMediaDataManager).addListener(listenerCaptor.capture());
+
+        final MediaDataManager.Listener listener = listenerCaptor.getValue();
+
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
+        listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+        verify(mDreamOverlayStateController, never()).addComplication(any());
+
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+        listener.onMediaDataRemoved(mKey);
+        verify(mDreamOverlayStateController, never()).removeComplication(any());
+
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
+        listener.onMediaDataRemoved(mKey);
+        verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt
new file mode 100644
index 0000000..c261086
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt
@@ -0,0 +1,124 @@
+package com.android.systemui.media.nearby
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider
+import com.android.systemui.shared.media.INearbyMediaDevicesService
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_LONG
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
+import com.android.systemui.shared.media.NearbyDevice
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class NearbyMediaDevicesServiceTest : SysuiTestCase() {
+
+    private lateinit var service: NearbyMediaDevicesService
+    private lateinit var binderInterface: INearbyMediaDevicesService
+
+    @Before
+    fun setUp() {
+        service = NearbyMediaDevicesService()
+        binderInterface = INearbyMediaDevicesService.Stub.asInterface(service.onBind(null))
+    }
+
+    @Test
+    fun getCurrentNearbyDevices_noProviderRegistered_returnsEmptyList() {
+        assertThat(service.getCurrentNearbyDevices()).isEmpty()
+    }
+
+    @Test
+    fun getCurrentNearbyDevices_providerRegistered_returnsProviderInfo() {
+        val nearbyDevice1 = NearbyDevice("routeId1", RANGE_LONG)
+        val nearbyDevice2 = NearbyDevice("routeId2", RANGE_WITHIN_REACH)
+        val provider = object : INearbyMediaDevicesProvider.Stub() {
+            override fun getCurrentNearbyDevices(): List<NearbyDevice> {
+                return listOf(nearbyDevice1, nearbyDevice2)
+            }
+
+            override fun registerNearbyDevicesCallback(
+                callback: INearbyMediaDevicesUpdateCallback?
+            ) {}
+            override fun unregisterNearbyDevicesCallback(
+                callback: INearbyMediaDevicesUpdateCallback?
+            ) {}
+        }
+        binderInterface.registerProvider(provider)
+
+        val returnedNearbyDevices = service.getCurrentNearbyDevices()
+
+        assertThat(returnedNearbyDevices).isEqualTo(listOf(nearbyDevice1, nearbyDevice2))
+    }
+
+    @Test
+    fun registerNearbyDevicesCallback_noProviderRegistered_noCrash() {
+        // No assert, just needs no crash
+        service.registerNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
+            override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+        })
+    }
+
+    @Test
+    fun registerNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
+        val provider = object : INearbyMediaDevicesProvider.Stub() {
+            var registeredCallback: INearbyMediaDevicesUpdateCallback? = null
+            override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
+
+            override fun registerNearbyDevicesCallback(
+                callback: INearbyMediaDevicesUpdateCallback?
+            ) {
+                registeredCallback = callback
+            }
+
+            override fun unregisterNearbyDevicesCallback(
+                callback: INearbyMediaDevicesUpdateCallback?
+            ) {}
+        }
+        binderInterface.registerProvider(provider)
+
+        val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
+            override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+        }
+
+        service.registerNearbyDevicesCallback(callback)
+
+        assertThat(provider.registeredCallback).isEqualTo(callback)
+    }
+
+    @Test
+    fun unregisterNearbyDevicesCallback_noProviderRegistered_noCrash() {
+        // No assert, just needs no crash
+        service.unregisterNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
+            override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+        })
+    }
+
+    @Test
+    fun unregisterNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
+        val provider = object : INearbyMediaDevicesProvider.Stub() {
+            var unregisteredCallback: INearbyMediaDevicesUpdateCallback? = null
+            override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
+
+            override fun registerNearbyDevicesCallback(
+                callback: INearbyMediaDevicesUpdateCallback?
+            ) {}
+
+            override fun unregisterNearbyDevicesCallback(
+                callback: INearbyMediaDevicesUpdateCallback?
+            ) {
+                unregisteredCallback = callback
+            }
+        }
+        binderInterface.registerProvider(provider)
+
+        val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
+            override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+        }
+
+        service.unregisterNearbyDevicesCallback(callback)
+
+        assertThat(provider.unregisteredCallback).isEqualTo(callback)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index 81ae209..5f800eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -21,9 +21,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
-import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderService
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.mockito.any
@@ -54,19 +51,10 @@
 
     @Mock
     private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver
-    @Mock
-    private lateinit var mediaSenderService: IDeviceSenderService.Stub
-    private lateinit var mediaSenderServiceComponentName: ComponentName
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        mediaSenderServiceComponentName = ComponentName(context, MediaTttSenderService::class.java)
-        context.addMockService(mediaSenderServiceComponentName, mediaSenderService)
-        whenever(mediaSenderService.queryLocalInterface(anyString())).thenReturn(mediaSenderService)
-        whenever(mediaSenderService.asBinder()).thenReturn(mediaSenderService)
-
         mediaTttCommandLineHelper =
             MediaTttCommandLineHelper(
                 commandRegistry,
@@ -100,6 +88,7 @@
         ) { EmptyCommand() }
     }
 
+    /* TODO(b/216318437): Revive these tests using the new SystemApis.
     @Test
     fun sender_moveCloserToStartCast_serviceCallbackCalled() {
         commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand())
@@ -182,6 +171,8 @@
         verify(mediaSenderService).noLongerCloseToReceiver(any(), any())
     }
 
+     */
+
     @Test
     fun receiver_addCommand_chipAdded() {
         commandRegistry.onShellCommand(pw, arrayOf(ADD_CHIP_COMMAND_RECEIVER_TAG))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 6b4eebe..58f4818 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -24,9 +24,10 @@
 import android.widget.LinearLayout
 import android.widget.TextView
 import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
+import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -46,12 +47,14 @@
 
     @Mock
     private lateinit var windowManager: WindowManager
+    @Mock
+    private lateinit var commandQueue: CommandQueue
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
-        controllerSender = MediaTttChipControllerSender(context, windowManager)
+        controllerSender = MediaTttChipControllerSender(context, windowManager, commandQueue)
     }
 
     @Test
@@ -133,7 +136,7 @@
 
     @Test
     fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
-        val undoCallback = object : IUndoTransferCallback.Stub() {
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {}
         }
         controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
@@ -146,7 +149,7 @@
     @Test
     fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
         var undoCallbackCalled = false
-        val undoCallback = object : IUndoTransferCallback.Stub() {
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {
                 undoCallbackCalled = true
             }
@@ -160,7 +163,7 @@
 
     @Test
     fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
-        val undoCallback = object : IUndoTransferCallback.Stub() {
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {}
         }
         controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
@@ -194,7 +197,7 @@
 
     @Test
     fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
-        val undoCallback = object : IUndoTransferCallback.Stub() {
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {}
         }
         controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
@@ -207,7 +210,7 @@
     @Test
     fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
         var undoCallbackCalled = false
-        val undoCallback = object : IUndoTransferCallback.Stub() {
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {
                 undoCallbackCalled = true
             }
@@ -221,7 +224,7 @@
 
     @Test
     fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
-        val undoCallback = object : IUndoTransferCallback.Stub() {
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {}
         }
         controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
@@ -267,7 +270,7 @@
         controllerSender.displayChip(transferToReceiverTriggered())
         controllerSender.displayChip(
             transferToReceiverSucceeded(
-                object : IUndoTransferCallback.Stub() {
+                object : IUndoMediaTransferCallback.Stub() {
                     override fun onUndoTriggered() {}
                 }
             )
@@ -327,13 +330,13 @@
         TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC)
 
     /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferToReceiverSucceeded(undoCallback: IUndoTransferCallback? = null) =
+    private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
         TransferToReceiverSucceeded(
             appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
         )
 
     /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferToThisDeviceSucceeded(undoCallback: IUndoTransferCallback? = null) =
+    private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
         TransferToThisDeviceSucceeded(
             appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
deleted file mode 100644
index 64542cb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.android.systemui.media.taptotransfer.sender
-
-import android.media.MediaRoute2Info
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderService
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@Ignore("b/216286227")
-class MediaTttSenderServiceTest : SysuiTestCase() {
-
-    private lateinit var service: IDeviceSenderService
-
-    @Mock
-    private lateinit var controller: MediaTttChipControllerSender
-
-    private val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
-        .addFeature("feature")
-        .build()
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        val mediaTttSenderService = MediaTttSenderService(context, controller)
-        service = IDeviceSenderService.Stub.asInterface(mediaTttSenderService.onBind(null))
-    }
-
-    @Test
-    fun closeToReceiverToStartCast_controllerTriggeredWithCorrectState() {
-        val name = "Fake name"
-        service.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
-
-        val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>()
-        verify(controller).displayChip(capture(chipStateCaptor))
-
-        val chipState = chipStateCaptor.value!!
-        assertThat(chipState.getChipTextString(context)).contains(name)
-    }
-
-    @Test
-    fun closeToReceiverToEndCast_controllerTriggeredWithCorrectState() {
-        val name = "Fake name"
-        service.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name))
-
-        val chipStateCaptor = argumentCaptor<MoveCloserToEndCast>()
-        verify(controller).displayChip(capture(chipStateCaptor))
-
-        val chipState = chipStateCaptor.value!!
-        assertThat(chipState.getChipTextString(context)).contains(name)
-    }
-
-    @Test
-    fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() {
-        service.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name"))
-
-        verify(controller).displayChip(any<TransferToThisDeviceTriggered>())
-    }
-
-    @Test
-    fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() {
-        val name = "Fake name"
-        service.transferToReceiverTriggered(mediaInfo, DeviceInfo(name))
-
-        val chipStateCaptor = argumentCaptor<TransferToReceiverTriggered>()
-        verify(controller).displayChip(capture(chipStateCaptor))
-
-        val chipState = chipStateCaptor.value!!
-        assertThat(chipState.getChipTextString(context)).contains(name)
-    }
-
-    @Test
-    fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() {
-        val name = "Fake name"
-        val undoCallback = object : IUndoTransferCallback.Stub() {
-            override fun onUndoTriggered() {}
-        }
-        service.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name), undoCallback)
-
-        val chipStateCaptor = argumentCaptor<TransferToReceiverSucceeded>()
-        verify(controller).displayChip(capture(chipStateCaptor))
-
-        val chipState = chipStateCaptor.value!!
-        assertThat(chipState.getChipTextString(context)).contains(name)
-        assertThat(chipState.undoCallback).isEqualTo(undoCallback)
-    }
-
-    @Test
-    fun transferToThisDeviceSucceeded_controllerTriggeredWithCorrectState() {
-        val undoCallback = object : IUndoTransferCallback.Stub() {
-            override fun onUndoTriggered() {}
-        }
-        service.transferToThisDeviceSucceeded(mediaInfo, DeviceInfo("name"), undoCallback)
-
-        val chipStateCaptor = argumentCaptor<TransferToThisDeviceSucceeded>()
-        verify(controller).displayChip(capture(chipStateCaptor))
-
-        val chipState = chipStateCaptor.value!!
-        assertThat(chipState.undoCallback).isEqualTo(undoCallback)
-    }
-
-    @Test
-    fun transferFailed_controllerTriggeredWithTransferFailedState() {
-        service.transferFailed(mediaInfo, DeviceInfo("Fake name"))
-
-        verify(controller).displayChip(any<TransferFailed>())
-    }
-
-    @Test
-    fun noLongerCloseToReceiver_controllerRemoveChipTriggered() {
-        service.noLongerCloseToReceiver(mediaInfo, DeviceInfo("Fake name"))
-
-        verify(controller).removeChip()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 354bb51..f5fa0d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -15,9 +15,10 @@
 import com.android.systemui.Dependency
 import com.android.systemui.R
 import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.FooterActionsController.ExpansionState
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -54,12 +55,16 @@
     @Mock
     private lateinit var userInfoController: UserInfoController
     @Mock
+    private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
+    @Mock
     private lateinit var multiUserSwitchController: MultiUserSwitchController
     @Mock
     private lateinit var globalActionsDialog: GlobalActionsDialogLite
     @Mock
     private lateinit var uiEventLogger: UiEventLogger
     @Mock
+    private lateinit var featureFlags: FeatureFlags
+
     private lateinit var controller: FooterActionsController
 
     private val metricsLogger: MetricsLogger = FakeMetricsLogger()
@@ -76,15 +81,18 @@
         injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
         val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
 
+        whenever(multiUserSwitchControllerFactory.create(any()))
+                .thenReturn(multiUserSwitchController)
+        whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(false)
+
         view = LayoutInflater.from(context)
                 .inflate(R.layout.footer_actions, null) as FooterActionsView
 
-        controller = FooterActionsController(view, activityStarter,
-                userManager, userTracker, userInfoController, multiUserSwitchController,
+        controller = FooterActionsController(view, multiUserSwitchControllerFactory,
+                activityStarter, userManager, userTracker, userInfoController,
                 deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
-                globalActionsDialog, uiEventLogger, showPMLiteButton = true,
-                buttonsVisibleState = ExpansionState.EXPANDED, fakeSettings,
-                Handler(testableLooper.looper))
+                globalActionsDialog, uiEventLogger, showPMLiteButton = true, fakeSettings,
+                Handler(testableLooper.looper), featureFlags)
         controller.init()
         ViewUtils.attachView(view)
         // View looper is the testable looper associated with the test
@@ -98,7 +106,7 @@
 
     @Test
     fun testLogPowerMenuClick() {
-        controller.expanded = true
+        controller.visible = true
         falsingManager.setFalseTap(false)
 
         view.findViewById<View>(R.id.pm_lite).performClick()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index f43e68f..26aa37d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -61,12 +61,8 @@
     @Mock
     private ClipboardManager mClipboardManager;
     @Mock
-    private QuickQSPanelController mQuickQSPanelController;
-    @Mock
     private TextView mBuildText;
     @Mock
-    private FooterActionsController mFooterActionsController;
-    @Mock
     private FalsingManager mFalsingManager;
     @Mock
     private ActivityStarter mActivityStarter;
@@ -93,8 +89,7 @@
         when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton);
 
         mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager,
-                mActivityStarter, mQSPanelController, mQuickQSPanelController,
-                mFooterActionsController);
+                mActivityStarter, mQSPanelController);
 
         mController.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 59948d3..09c6d9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -68,8 +68,6 @@
     private lateinit var tileView: QSTileView
     @Mock
     private lateinit var quickQsBrightnessController: QuickQSBrightnessController
-    @Mock
-    private lateinit var footerActionsController: FooterActionsController
     @Captor
     private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
 
@@ -95,8 +93,7 @@
                 uiEventLogger,
                 qsLogger,
                 dumpManager,
-                quickQsBrightnessController,
-                footerActionsController
+                quickQsBrightnessController
         )
 
         controller.init()
@@ -128,14 +125,12 @@
     }
 
     @Test
-    fun testBrightnessAndFooterVisibilityRefreshedWhenConfigurationChanged() {
+    fun testBrightnessRefreshedWhenConfigurationChanged() {
         // times(2) because both controller and base controller are registering their listeners
         verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
 
         captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
 
         verify(quickQsBrightnessController).refreshVisibility(anyBoolean())
-        // times(2) because footer visibility is also refreshed on controller init
-        verify(footerActionsController, times(2)).refreshVisibility(anyBoolean())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
new file mode 100644
index 0000000..3231415
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -0,0 +1,117 @@
+package com.android.systemui.shared.animation
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
+
+    @Mock private lateinit var progressProvider: UnfoldTransitionProgressProvider
+
+    @Mock private lateinit var parent: ViewGroup
+
+    @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+
+    private lateinit var animator: UnfoldConstantTranslateAnimator
+    private lateinit var progressListener: TransitionProgressListener
+
+    private val viewsIdToRegister =
+        setOf(
+            ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT),
+            ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT))
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        animator =
+            UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider)
+
+        animator.init(parent, MAX_TRANSLATION)
+
+        verify(progressProvider).addCallback(progressListenerCaptor.capture())
+        progressListener = progressListenerCaptor.value
+    }
+
+    @Test
+    fun onTransition_noMatchingIds() {
+        // GIVEN no views matching any ids
+        // WHEN the transition starts
+        progressListener.onTransitionStarted()
+        progressListener.onTransitionProgress(.1f)
+
+        // THEN nothing... no exceptions
+    }
+
+    @Test
+    fun onTransition_oneMovesLeft() {
+        // GIVEN one view with a matching id
+        val view = View(context)
+        whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view)
+
+        moveAndValidate(listOf(view to LEFT))
+    }
+
+    @Test
+    fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
+        // GIVEN two views with a matching id
+        val leftView = View(context)
+        val rightView = View(context)
+        whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView)
+        whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView)
+
+        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+        moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+    }
+
+    private fun moveAndValidate(list: List<Pair<View, Int>>) {
+        // Compare values as ints because -0f != 0f
+
+        // WHEN the transition starts
+        progressListener.onTransitionStarted()
+        progressListener.onTransitionProgress(0f)
+
+        list.forEach { (view, direction) ->
+            assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
+        }
+
+        // WHEN the transition progresses, translation is updated
+        progressListener.onTransitionProgress(.5f)
+        list.forEach { (view, direction) ->
+            assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
+        }
+
+        // WHEN the transition ends, translation is completed
+        progressListener.onTransitionProgress(1f)
+        progressListener.onTransitionFinished()
+        list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) }
+    }
+
+    companion object {
+        private val LEFT = Direction.LEFT.multiplier.toInt()
+        private val RIGHT = Direction.RIGHT.multiplier.toInt()
+
+        private const val MAX_TRANSLATION = 42f
+
+        private const val LEFT_VIEW_ID = 1
+        private const val RIGHT_VIEW_ID = 2
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 42647f7..9076e16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -169,8 +169,6 @@
         transitionController.goToLockedShade(null)
         whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
         transitionController.goToLockedShade(null)
-        whenever(statusbarStateController.state).thenReturn(StatusBarState.FULLSCREEN_USER_SWITCHER)
-        transitionController.goToLockedShade(null)
         verify(statusbarStateController, never()).setState(anyInt())
     }
 
@@ -227,7 +225,7 @@
     fun testDragDownAmountDoesntCallOutInLockedDownShade() {
         whenever(nsslController.isInLockedDownShade).thenReturn(true)
         transitionController.dragDownAmount = 10f
-        verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
+        verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
         verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
         verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat())
         verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
@@ -238,7 +236,7 @@
     @Test
     fun testDragDownAmountCallsOut() {
         transitionController.dragDownAmount = 10f
-        verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
+        verify(nsslController).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
         verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
         verify(scrimController).setTransitionToFullShadeProgress(anyFloat())
         verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 83f1d87..7fafb24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -35,6 +35,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -56,6 +57,7 @@
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wm.shell.bubbles.Bubbles;
 
 import com.google.android.collect.Lists;
@@ -123,7 +125,9 @@
                 mock(DynamicChildBindController.class),
                 mock(LowPriorityInflationHelper.class),
                 mock(AssistantFeedbackController.class),
-                mNotifPipelineFlags);
+                mNotifPipelineFlags,
+                mock(KeyguardUpdateMonitor.class),
+                mock(KeyguardStateController.class));
         mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index b736f38..a5ea897 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -61,18 +61,16 @@
     @Test
     fun testChangeState_logged() {
         TestableLooper.get(this).runWithLooper {
-            controller.state = StatusBarState.FULLSCREEN_USER_SWITCHER
             controller.state = StatusBarState.KEYGUARD
             controller.state = StatusBarState.SHADE
             controller.state = StatusBarState.SHADE_LOCKED
         }
 
         val logs = uiEventLogger.logs
-        assertEquals(4, logs.size)
+        assertEquals(3, logs.size)
         val ids = logs.map(UiEventLoggerFake.FakeUiEvent::eventId)
-        assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER.id, ids[0])
-        assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD.id, ids[1])
-        assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE.id, ids[2])
-        assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED.id, ids[3])
+        assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD.id, ids[0])
+        assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE.id, ids[1])
+        assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED.id, ids[2])
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
index b5b2f1f..79a2008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
@@ -33,18 +33,16 @@
                 StatusBarStateEvent.STATUS_BAR_STATE_SHADE,
                 StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED,
                 StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD,
-                StatusBarStateEvent.STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER,
                 StatusBarStateEvent.STATUS_BAR_STATE_UNKNOWN
         )
         val states = listOf(
                 StatusBarState.SHADE,
                 StatusBarState.SHADE_LOCKED,
                 StatusBarState.KEYGUARD,
-                StatusBarState.FULLSCREEN_USER_SWITCHER,
                 -1
         )
         events.zip(states).forEach { (event, state) ->
             assertEquals(event, StatusBarStateEvent.fromState(state))
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 5fd4174..3f84c16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -19,8 +19,11 @@
 import android.os.UserHandle
 import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.DynamicPrivacyController
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -28,9 +31,12 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import org.junit.Test
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 
@@ -40,9 +46,13 @@
     val dynamicPrivacyController: DynamicPrivacyController = mock()
     val lockscreenUserManager: NotificationLockscreenUserManager = mock()
     val pipeline: NotifPipeline = mock()
+    val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
+    val statusBarStateController: StatusBarStateController = mock()
+    val keyguardStateController: KeyguardStateController = mock()
 
     val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule
-            .provideCoordinator(dynamicPrivacyController, lockscreenUserManager)
+            .provideCoordinator(dynamicPrivacyController, lockscreenUserManager,
+            keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
 
     @Test
     fun onDynamicPrivacyChanged_invokeInvalidationListener() {
@@ -190,6 +200,28 @@
         verify(entry.representativeEntry!!).setSensitive(true, true)
     }
 
+    @Test
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
+                .thenReturn(true)
+
+        val entry = fakeNotification(2, true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!, never()).setSensitive(any(), any())
+    }
+
     private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
         val mockUserHandle = mock<UserHandle>().apply {
             whenever(identifier).thenReturn(notifUserId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
index 15ff555..ab71264 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.render;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
@@ -138,7 +139,7 @@
     }
 
     @Test
-    public void testRemovedGroupsAreKeptTogether() {
+    public void testRemovedGroupsAreBrokenApart() {
         // GIVEN a preexisting tree with a group
         applySpecAndCheck(
                 node(mController1),
@@ -154,10 +155,10 @@
                 node(mController1)
         );
 
-        // THEN the group children are still attached to their parent
-        assertEquals(mController2.getView(), mController3.getView().getParent());
-        assertEquals(mController2.getView(), mController4.getView().getParent());
-        assertEquals(mController2.getView(), mController5.getView().getParent());
+        // THEN the group children are no longer attached to their parent
+        assertNull(mController3.getView().getParent());
+        assertNull(mController4.getView().getParent());
+        assertNull(mController5.getView().getParent());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
new file mode 100644
index 0000000..d280f54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -0,0 +1,153 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationShelf
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.*
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Tests for {@link NotificationShelf}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfTest : SysuiTestCase() {
+
+    private val shelf = NotificationShelf(context, /* attrs */ null)
+    private val shelfState = shelf.viewState as NotificationShelf.ShelfState
+    private val ambientState = mock(AmbientState::class.java)
+
+    @Before
+    fun setUp() {
+        shelf.bind(ambientState, /* hostLayoutController */ null)
+        shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+    }
+
+    @Test
+    fun testShadeWidth_BasedOnFractionToShade() {
+        setFractionToShade(0f)
+        setOnLockscreen(true)
+
+        shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+        assertTrue(shelfState.actualWidth == 10)
+
+        shelf.updateStateWidth(shelfState, /* fraction */ 0.5f, /* shortestWidth */ 10)
+        assertTrue(shelfState.actualWidth == 20)
+
+        shelf.updateStateWidth(shelfState, /* fraction */ 1f, /* shortestWidth */ 10)
+        assertTrue(shelfState.actualWidth == 30)
+    }
+
+    @Test
+    fun testShelfIsLong_WhenNotOnLockscreen() {
+        setFractionToShade(0f)
+        setOnLockscreen(false)
+
+        shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+        assertTrue(shelfState.actualWidth == 30)
+    }
+
+    @Test
+    fun testX_inViewForClick() {
+        val isXInView = shelf.isXInView(
+                /* localX */ 5f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertTrue(isXInView)
+    }
+
+    @Test
+    fun testXSlop_inViewForClick() {
+        val isLeftXSlopInView = shelf.isXInView(
+                /* localX */ -3f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertTrue(isLeftXSlopInView)
+
+        val isRightXSlopInView = shelf.isXInView(
+                /* localX */ 13f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertTrue(isRightXSlopInView)
+    }
+
+    @Test
+    fun testX_notInViewForClick() {
+        val isXLeftOfShelfInView = shelf.isXInView(
+                /* localX */ -10f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertFalse(isXLeftOfShelfInView)
+
+        val isXRightOfShelfInView = shelf.isXInView(
+                /* localX */ 20f,
+                /* slop */ 5f,
+                /* left */ 0f,
+                /* right */ 10f)
+        assertFalse(isXRightOfShelfInView)
+    }
+
+    @Test
+    fun testY_inViewForClick() {
+        val isYInView = shelf.isYInView(
+                /* localY */ 5f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 10f)
+        assertTrue(isYInView)
+    }
+
+    @Test
+    fun testYSlop_inViewForClick() {
+        val isTopYSlopInView = shelf.isYInView(
+                /* localY */ -3f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 10f)
+        assertTrue(isTopYSlopInView)
+
+        val isBottomYSlopInView = shelf.isYInView(
+                /* localY */ 13f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 10f)
+        assertTrue(isBottomYSlopInView)
+    }
+
+    @Test
+    fun testY_notInViewForClick() {
+        val isYAboveShelfInView = shelf.isYInView(
+                /* localY */ -10f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 5f)
+        assertFalse(isYAboveShelfInView)
+
+        val isYBelowShelfInView = shelf.isYInView(
+                /* localY */ 15f,
+                /* slop */ 5f,
+                /* top */ 0f,
+                /* bottom */ 5f)
+        assertFalse(isYBelowShelfInView)
+    }
+
+    private fun setFractionToShade(fraction: Float) {
+        shelf.setFractionToShade(fraction)
+    }
+
+    private fun setOnLockscreen(isOnLockscreen: Boolean) {
+        whenever(ambientState.isOnKeyguard).thenReturn(isOnLockscreen)
+    }
+}
\ No newline at end of file
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 ea68143..1da9bbc 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
@@ -69,10 +69,9 @@
 
         stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
 
-        val closeHandleUnderlapHeight =
-            context.resources.getDimensionPixelSize(R.dimen.close_handle_underlap)
-        val fullHeight =
-            ambientState.layoutMaxHeight + closeHandleUnderlapHeight - ambientState.stackY
+        val marginBottom =
+            context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+        val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
         val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
         assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
     }
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 8c7d22d..fb232ba 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
@@ -38,6 +38,7 @@
 import android.testing.TestableResources;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -110,6 +111,8 @@
     private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock
     private SessionTracker mSessionTracker;
+    @Mock
+    private LatencyTracker mLatencyTracker;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
@@ -133,7 +136,7 @@
                 mMetricsLogger, mDumpManager, mPowerManager,
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
                 mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
-                mSessionTracker);
+                mSessionTracker, mLatencyTracker);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 36a4c1e..01e9822e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -23,9 +23,13 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
@@ -45,6 +49,7 @@
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
@@ -52,6 +57,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;
 
@@ -87,6 +93,12 @@
     private SysuiStatusBarStateController mStatusBarStateController;
     @Mock
     private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
+    @Mock
+    private UserManager mUserManager;
+    @Captor
+    private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
+    @Captor
+    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
 
     private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
     private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -101,8 +113,8 @@
         allowTestableLooperAsMainThread();
         TestableLooper.get(this).runWithLooper(() -> {
             mKeyguardStatusBarView =
-                    (KeyguardStatusBarView) LayoutInflater.from(mContext)
-                            .inflate(R.layout.keyguard_status_bar, null);
+                    spy((KeyguardStatusBarView) LayoutInflater.from(mContext)
+                            .inflate(R.layout.keyguard_status_bar, null));
         });
 
         mController = new KeyguardStatusBarViewController(
@@ -121,7 +133,8 @@
                 mKeyguardUpdateMonitor,
                 mBiometricUnlockController,
                 mStatusBarStateController,
-                mStatusBarContentInsetsProvider
+                mStatusBarContentInsetsProvider,
+                mUserManager
         );
     }
 
@@ -133,6 +146,31 @@
         verify(mAnimationScheduler).addCallback(any());
         verify(mUserInfoController).addCallback(any());
         verify(mStatusBarIconController).addIconGroup(any());
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+    }
+
+    @Test
+    public void onConfigurationChanged_updatesUserSwitcherVisibility() {
+        mController.onViewAttached();
+        verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
+        clearInvocations(mUserManager);
+        clearInvocations(mKeyguardStatusBarView);
+
+        mConfigurationListenerCaptor.getValue().onConfigChanged(null);
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+    }
+
+    @Test
+    public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() {
+        mController.onViewAttached();
+        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
+        clearInvocations(mUserManager);
+        clearInvocations(mKeyguardStatusBarView);
+
+        mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 5d7b154..d002cebe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -250,15 +250,17 @@
             @Notification.GroupAlertBehavior int priorityGroupAlert,
             @Notification.GroupAlertBehavior int siblingGroupAlert,
             boolean expectAlertOverride) {
+        long when = 10000;
         // Create entries in an order so that the priority entry can be deemed the newest child.
         NotificationEntry[] siblings = new NotificationEntry[numSiblings];
         for (int i = 0; i < numSiblings; i++) {
-            siblings[i] = mGroupTestHelper.createChildNotification(siblingGroupAlert, i, "sibling");
+            siblings[i] = mGroupTestHelper
+                    .createChildNotification(siblingGroupAlert, i, "sibling", ++when);
         }
         NotificationEntry priorityEntry =
-                mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority");
+                mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority", ++when);
         NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary");
+                mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary", ++when);
 
         // The priority entry is an important conversation.
         when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
@@ -322,17 +324,19 @@
 
     @Test
     public void testAlertOverrideWhenUpdatingSummaryAtEnd() {
+        long when = 10000;
         int numSiblings = 2;
         int groupAlert = Notification.GROUP_ALERT_SUMMARY;
         // Create entries in an order so that the priority entry can be deemed the newest child.
         NotificationEntry[] siblings = new NotificationEntry[numSiblings];
         for (int i = 0; i < numSiblings; i++) {
-            siblings[i] = mGroupTestHelper.createChildNotification(groupAlert, i, "sibling");
+            siblings[i] =
+                    mGroupTestHelper.createChildNotification(groupAlert, i, "sibling", ++when);
         }
         NotificationEntry priorityEntry =
-                mGroupTestHelper.createChildNotification(groupAlert, 0, "priority");
+                mGroupTestHelper.createChildNotification(groupAlert, 0, "priority", ++when);
         NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary");
+                mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary", ++when);
 
         // The priority entry is an important conversation.
         when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
index 337e64592..bbb2346 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -6,6 +6,8 @@
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
 import com.android.systemui.recents.OverviewProxyService
@@ -46,6 +48,8 @@
     private lateinit var overviewProxyService: OverviewProxyService
     @Mock
     private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
     @Captor
     lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
     @Captor
@@ -64,7 +68,8 @@
         notificationsQSContainerController = NotificationsQSContainerController(
                 notificationsQSContainer,
                 navigationModeController,
-                overviewProxyService
+                overviewProxyService,
+                featureFlags
         )
         whenever(notificationsQSContainer.defaultNotificationsMarginBottom)
                 .thenReturn(NOTIFICATIONS_MARGIN)
@@ -85,6 +90,26 @@
     @Test
     fun testTaskbarVisibleInSplitShade() {
         notificationsQSContainerController.splitShadeEnabled = true
+        useNewFooter(false)
+
+        given(taskbarVisible = true,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+
+        given(taskbarVisible = true,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
+                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+    }
+
+    @Test
+    fun testTaskbarVisibleInSplitShade_newFooter() {
+        notificationsQSContainerController.splitShadeEnabled = true
+        useNewFooter(true)
+
         given(taskbarVisible = true,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
@@ -102,6 +127,26 @@
     fun testTaskbarNotVisibleInSplitShade() {
         // when taskbar is not visible, it means we're on the home screen
         notificationsQSContainerController.splitShadeEnabled = true
+        useNewFooter(false)
+
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShade_newFooter() {
+        // when taskbar is not visible, it means we're on the home screen
+        notificationsQSContainerController.splitShadeEnabled = true
+        useNewFooter(true)
+
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
@@ -117,6 +162,25 @@
     @Test
     fun testTaskbarNotVisibleInSplitShadeWithCutout() {
         notificationsQSContainerController.splitShadeEnabled = true
+        useNewFooter(false)
+
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withCutout())
+        then(expectedContainerPadding = CUTOUT_HEIGHT)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withCutout().withStableBottom())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShadeWithCutout_newFooter() {
+        notificationsQSContainerController.splitShadeEnabled = true
+        useNewFooter(true)
+
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withCutout())
@@ -132,6 +196,24 @@
     @Test
     fun testTaskbarVisibleInSinglePaneShade() {
         notificationsQSContainerController.splitShadeEnabled = false
+        useNewFooter(false)
+
+        given(taskbarVisible = true,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0)
+
+        given(taskbarVisible = true,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = STABLE_INSET_BOTTOM)
+    }
+
+    @Test
+    fun testTaskbarVisibleInSinglePaneShade_newFooter() {
+        notificationsQSContainerController.splitShadeEnabled = false
+        useNewFooter(true)
+
         given(taskbarVisible = true,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
@@ -146,6 +228,8 @@
     @Test
     fun testTaskbarNotVisibleInSinglePaneShade() {
         notificationsQSContainerController.splitShadeEnabled = false
+        useNewFooter(false)
+
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = emptyInsets())
@@ -159,14 +243,56 @@
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedQsPadding = STABLE_INSET_BOTTOM)
+        then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSinglePaneShade_newFooter() {
+        notificationsQSContainerController.splitShadeEnabled = false
+        useNewFooter(true)
+
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withCutout().withStableBottom())
+        then(expectedContainerPadding = CUTOUT_HEIGHT)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
     fun testCustomizingInSinglePaneShade() {
         notificationsQSContainerController.splitShadeEnabled = false
         notificationsQSContainerController.setCustomizerShowing(true)
+        useNewFooter(false)
+
+        // always sets spacings to 0
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = 0)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = emptyInsets())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = 0)
+    }
+
+    @Test
+    fun testCustomizingInSinglePaneShade_newFooter() {
+        notificationsQSContainerController.splitShadeEnabled = false
+        notificationsQSContainerController.setCustomizerShowing(true)
+        useNewFooter(true)
+
         // always sets spacings to 0
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
@@ -185,6 +311,28 @@
     fun testDetailShowingInSinglePaneShade() {
         notificationsQSContainerController.splitShadeEnabled = false
         notificationsQSContainerController.setDetailShowing(true)
+        useNewFooter(false)
+
+        // always sets spacings to 0
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = 0)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = emptyInsets())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = 0)
+    }
+
+    @Test
+    fun testDetailShowingInSinglePaneShade_newFooter() {
+        notificationsQSContainerController.splitShadeEnabled = false
+        notificationsQSContainerController.setDetailShowing(true)
+        useNewFooter(true)
+
         // always sets spacings to 0
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
@@ -202,6 +350,8 @@
     @Test
     fun testDetailShowingInSplitShade() {
         notificationsQSContainerController.splitShadeEnabled = true
+        useNewFooter(false)
+
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
@@ -215,6 +365,36 @@
         then(expectedContainerPadding = 0)
     }
 
+    @Test
+    fun testDetailShowingInSplitShade_newFooter() {
+        notificationsQSContainerController.splitShadeEnabled = true
+        useNewFooter(true)
+
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0)
+
+        notificationsQSContainerController.setDetailShowing(true)
+        // should not influence spacing
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+    }
+
+    @Test
+    fun testNotificationsMarginBottomIsUpdated() {
+        notificationsQSContainerController.splitShadeEnabled = true
+        verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+        whenever(notificationsQSContainer.defaultNotificationsMarginBottom).thenReturn(100)
+        notificationsQSContainerController.updateMargins()
+        notificationsQSContainerController.splitShadeEnabled = false
+
+        verify(notificationsQSContainer).setNotificationsMarginBottom(100)
+    }
+
     private fun given(
         taskbarVisible: Boolean,
         navigationMode: Int,
@@ -234,7 +414,13 @@
         verify(notificationsQSContainer)
                 .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
         verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
-        verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
+        val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
+        if (newFooter) {
+            verify(notificationsQSContainer)
+                    .setQSContainerPaddingBottom(expectedNotificationsMargin)
+        } else {
+            verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
+        }
         Mockito.clearInvocations(notificationsQSContainer)
     }
 
@@ -251,4 +437,8 @@
         whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
         return this
     }
+
+    private fun useNewFooter(useNewFooter: Boolean) {
+        whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(useNewFooter)
+    }
 }
\ No newline at end of file
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 5d80bca..107ba81 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
@@ -36,6 +36,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardMessageAreaController;
@@ -43,6 +44,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -97,6 +99,10 @@
     private KeyguardMessageArea mKeyguardMessageArea;
     @Mock
     private ShadeController mShadeController;
+    @Mock
+    private DreamOverlayStateController mDreamOverlayStateController;
+    @Mock
+    private LatencyTracker mLatencyTracker;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
@@ -116,6 +122,7 @@
                 mStatusBarStateController,
                 mock(ConfigurationController.class),
                 mKeyguardUpdateMonitor,
+                mDreamOverlayStateController,
                 mock(NavigationModeController.class),
                 mock(DockManager.class),
                 mock(NotificationShadeWindowController.class),
@@ -123,7 +130,8 @@
                 mock(NotificationMediaManager.class),
                 mKeyguardBouncerFactory,
                 mKeyguardMessageAreaFactory,
-                () -> mShadeController);
+                () -> mShadeController,
+                mLatencyTracker);
         mStatusBarKeyguardViewManager.registerStatusBar(
                 mStatusBar,
                 mNotificationPanelView,
@@ -167,17 +175,6 @@
     }
 
     @Test
-    public void onPanelExpansionChanged_neverHidesFullscreenBouncer() {
-        // TODO: StatusBar should not be here, mBouncer.isFullscreenBouncer() should do the same.
-        when(mStatusBar.isFullScreenUserSwitcherState()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
-                /* fraction= */ 0.5f,
-                /* expanded= */ false,
-                /* tracking= */ true);
-        verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
-    }
-
-    @Test
     public void onPanelExpansionChanged_neverHidesScrimmedBouncer() {
         when(mBouncer.isShowing()).thenReturn(true);
         when(mBouncer.isScrimmed()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index b7c00fe..90b93e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
@@ -285,6 +286,7 @@
     @Mock private NotifLiveDataStore mNotifLiveDataStore;
     @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private DeviceStateManager mDeviceStateManager;
+    @Mock private DreamOverlayStateController mDreamOverlayStateController;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -474,7 +476,8 @@
                 mActivityLaunchAnimator,
                 mNotifPipelineFlags,
                 mJankMonitor,
-                mDeviceStateManager);
+                mDeviceStateManager,
+                mDreamOverlayStateController);
         when(mKeyguardViewMediator.registerStatusBar(
                 any(StatusBar.class),
                 any(NotificationPanelViewController.class),
@@ -882,15 +885,6 @@
     }
 
     @Test
-    public void testSetState_changesIsFullScreenUserSwitcherState() {
-        mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
-        assertFalse(mStatusBar.isFullScreenUserSwitcherState());
-
-        mStatusBar.setBarStateForTest(StatusBarState.FULLSCREEN_USER_SWITCHER);
-        assertTrue(mStatusBar.isFullScreenUserSwitcherState());
-    }
-
-    @Test
     public void testShowKeyguardImplementation_setsState() {
         when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>());
 
@@ -900,12 +894,6 @@
         mStatusBar.showKeyguardImpl();
         verify(mStatusBarStateController).setState(
                 eq(StatusBarState.KEYGUARD), eq(false) /* force */);
-
-        // If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER.
-        when(mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true);
-        mStatusBar.showKeyguardImpl();
-        verify(mStatusBarStateController).setState(
-                eq(StatusBarState.FULLSCREEN_USER_SWITCHER), eq(false) /* force */);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index fa2a906..9a7e702 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -400,4 +400,16 @@
 
         assertEquals(fgUserName, userSwitcherController.currentUserName)
     }
+
+    @Test
+    fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
+        `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
+        assertEquals(true, userSwitcherController.isSystemUser)
+    }
+
+    @Test
+    fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
+        `when`(userTracker.userId).thenReturn(1)
+        assertEquals(false, userSwitcherController.isSystemUser)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
new file mode 100644
index 0000000..a2fd288
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.service;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PackageObserverTest extends SysuiTestCase {
+    @Mock
+    Context mContext;
+
+    @Mock
+    Observer.Callback mCallback;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testChange() {
+        final PackageObserver observer = new PackageObserver(mContext,
+                ComponentName.unflattenFromString("com.foo.bar/baz"));
+        final ArgumentCaptor<BroadcastReceiver> receiverCapture =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        observer.addCallback(mCallback);
+
+        // Verify broadcast receiver registered.
+        verify(mContext).registerReceiver(receiverCapture.capture(), any(), anyInt());
+
+        // Simulate package change.
+        receiverCapture.getValue().onReceive(mContext, new Intent());
+
+        // Check that callback was informed.
+        verify(mCallback).onSourceChanged();
+
+        observer.removeCallback(mCallback);
+
+        // Make sure receiver is unregistered on last callback removal
+        verify(mContext).unregisterReceiver(receiverCapture.getValue());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
new file mode 100644
index 0000000..53d4a96
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.service;
+
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PersistentConnectionManagerTest extends SysuiTestCase {
+    private static final int MAX_RETRIES = 5;
+    private static final int RETRY_DELAY_MS = 1000;
+    private static final int CONNECTION_MIN_DURATION_MS = 5000;
+
+    private FakeSystemClock mFakeClock = new FakeSystemClock();
+    private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
+
+    @Mock
+    private ObservableServiceConnection<Proxy> mConnection;
+
+    @Mock
+    private Observer mObserver;
+
+    private static class Proxy {
+    }
+
+    private PersistentConnectionManager<Proxy> mConnectionManager;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mConnectionManager = new PersistentConnectionManager<>(
+                mFakeClock,
+                mFakeExecutor,
+                mConnection,
+                MAX_RETRIES,
+                RETRY_DELAY_MS,
+                CONNECTION_MIN_DURATION_MS,
+                mObserver);
+    }
+
+    private ObservableServiceConnection.Callback<Proxy> captureCallbackAndSend(
+            ObservableServiceConnection<Proxy> mConnection, Proxy proxy) {
+        ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+                ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+        verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+        verify(mConnection).bind();
+        Mockito.clearInvocations(mConnection);
+
+        final ObservableServiceConnection.Callback callback = connectionCallbackCaptor.getValue();
+        if (proxy != null) {
+            callback.onConnected(mConnection, proxy);
+        } else {
+            callback.onDisconnected(mConnection, 0);
+        }
+
+        return callback;
+    }
+
+    /**
+     * Validates initial connection.
+     */
+    @Test
+    public void testConnect() {
+        mConnectionManager.start();
+        captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
+    }
+
+    /**
+     * Ensures reconnection on disconnect.
+     */
+    @Test
+    public void testRetryOnBindFailure() {
+        mConnectionManager.start();
+        ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+                ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+        verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+
+        // Verify attempts happen. Note that we account for the retries plus initial attempt, which
+        // is not scheduled.
+        for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
+            verify(mConnection).bind();
+            Mockito.clearInvocations(mConnection);
+            connectionCallbackCaptor.getValue().onDisconnected(mConnection, 0);
+            mFakeExecutor.advanceClockToNext();
+            mFakeExecutor.runAllReady();
+        }
+    }
+
+    /**
+     * Ensures rebind on package change.
+     */
+    @Test
+    public void testAttemptOnPackageChange() {
+        mConnectionManager.start();
+        verify(mConnection).bind();
+        ArgumentCaptor<Observer.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(Observer.Callback.class);
+        captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
+
+        verify(mObserver).addCallback(callbackCaptor.capture());
+
+        callbackCaptor.getValue().onSourceChanged();
+        verify(mConnection).bind();
+    }
+}
diff --git a/services/Android.bp b/services/Android.bp
index af70692..b0a5c66 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -102,6 +102,7 @@
         ":services.usage-sources",
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
+        ":services.wallpapereffectsgeneration-sources",
         ":services.wifi-sources",
     ],
     visibility: ["//visibility:private"],
@@ -158,6 +159,7 @@
         "services.usage",
         "services.usb",
         "services.voiceinteraction",
+        "services.wallpapereffectsgeneration",
         "services.wifi",
         "service-blobstore",
         "service-jobscheduler",
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 6744ea8..803177b 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -295,6 +295,21 @@
                 case AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK :
                     sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
                     return true;
+                case AccessibilityService.GLOBAL_ACTION_DPAD_UP:
+                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_UP);
+                    return true;
+                case AccessibilityService.GLOBAL_ACTION_DPAD_DOWN:
+                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_DOWN);
+                    return true;
+                case AccessibilityService.GLOBAL_ACTION_DPAD_LEFT:
+                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
+                    return true;
+                case AccessibilityService.GLOBAL_ACTION_DPAD_RIGHT:
+                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT);
+                    return true;
+                case AccessibilityService.GLOBAL_ACTION_DPAD_CENTER:
+                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
+                    return true;
                 default:
                     Slog.e(TAG, "Invalid action id: " + actionId);
                     return false;
diff --git a/services/api/current.txt b/services/api/current.txt
index 50f0052..dcf7e64 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -39,6 +39,7 @@
 
   public interface ActivityManagerLocal {
     method public boolean canStartForegroundService(int, int, @NonNull String);
+    method public boolean startAndBindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int) throws android.os.TransactionTooLargeException;
   }
 
 }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 2168fb1..a65d5b3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -3346,8 +3346,10 @@
             // Isolate the changes relating to RROs. The app info must be copied to prevent
             // affecting other parts of system server that may have cached this app info.
             oldAppInfo = new ApplicationInfo(oldAppInfo);
-            oldAppInfo.overlayPaths = newAppInfo.overlayPaths.clone();
-            oldAppInfo.resourceDirs = newAppInfo.resourceDirs.clone();
+            oldAppInfo.overlayPaths = newAppInfo.overlayPaths == null
+                    ? null : newAppInfo.overlayPaths.clone();
+            oldAppInfo.resourceDirs = newAppInfo.resourceDirs == null
+                    ? null : newAppInfo.resourceDirs.clone();
             provider.info.providerInfo.applicationInfo = oldAppInfo;
 
             for (int j = 0, M = provider.widgets.size(); j < M; j++) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 095c1fc..76ee728 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -17,6 +17,7 @@
 package com.android.server.autofill;
 
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
+import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
 import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -47,13 +48,16 @@
 import android.app.Activity;
 import android.app.ActivityTaskManager;
 import android.app.IAssistDataReceiver;
+import android.app.PendingIntent;
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.AutofillOverlay;
 import android.app.assist.AssistStructure.ViewNode;
+import android.content.BroadcastReceiver;
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.graphics.Bitmap;
@@ -148,6 +152,8 @@
         AutoFillUI.AutoFillUiCallback, ValueFinder {
     private static final String TAG = "AutofillSession";
 
+    private static final String ACTION_DELAYED_FILL =
+            "android.service.autofill.action.DELAYED_FILL";
     private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
 
     final Object mLock;
@@ -155,6 +161,7 @@
     private final AutofillManagerServiceImpl mService;
     private final Handler mHandler;
     private final AutoFillUI mUi;
+    @NonNull private final Context mContext;
 
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
@@ -269,6 +276,12 @@
      */
     private boolean mHasCallback;
 
+    @GuardedBy("mLock")
+    private boolean mDelayedFillBroadcastReceiverRegistered;
+
+    @GuardedBy("mLock")
+    private PendingIntent mDelayedFillPendingIntent;
+
     /**
      * Extras sent by service on {@code onFillRequest()} calls; the most recent non-null extra is
      * saved and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
@@ -356,6 +369,32 @@
 
     private final AccessibilityManager mAccessibilityManager;
 
+    // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
+    // new one per Session.
+    private final BroadcastReceiver mDelayedFillBroadcastReceiver =
+            new BroadcastReceiver() {
+                // ErrorProne says mAssistReceiver#processDelayedFillLocked needs to be guarded by
+                // 'Session.this.mLock', which is the same as mLock.
+                @SuppressWarnings("GuardedBy")
+                @Override
+                public void onReceive(final Context context, final Intent intent) {
+                    if (!intent.getAction().equals(ACTION_DELAYED_FILL)) {
+                        Slog.wtf(TAG, "Unexpected action is received.");
+                        return;
+                    }
+                    if (!intent.hasExtra(EXTRA_REQUEST_ID)) {
+                        Slog.e(TAG, "Delay fill action is missing request id extra.");
+                        return;
+                    }
+                    Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received");
+                    synchronized (mLock) {
+                        int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
+                        FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
+                        mAssistReceiver.processDelayedFillLocked(requestId, response);
+                    }
+                }
+            };
+
     void onSwitchInputMethodLocked() {
         // One caveat is that for the case where the focus is on a field for which regular autofill
         // returns null, and augmented autofill is triggered,  and then the user switches the input
@@ -408,31 +447,24 @@
      */
     private final class SessionFlags {
         /** Whether autofill is disabled by the service */
-        @GuardedBy("mLock")
         private boolean mAutofillDisabled;
 
         /** Whether the autofill service supports inline suggestions */
-        @GuardedBy("mLock")
         private boolean mInlineSupportedByService;
 
         /** True if session is for augmented only */
-        @GuardedBy("mLock")
         private boolean mAugmentedAutofillOnly;
 
         /** Whether the session is currently showing the SaveUi. */
-        @GuardedBy("mLock")
         private boolean mShowingSaveUi;
 
         /** Whether the current {@link FillResponse} is expired. */
-        @GuardedBy("mLock")
         private boolean mExpiredResponse;
 
         /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
-        @GuardedBy("mLock")
         private boolean mClientSuggestionsEnabled;
 
         /** Whether the fill dialog UI is disabled. */
-        @GuardedBy("mLock")
         private boolean mFillDialogDisabled;
     }
 
@@ -447,6 +479,8 @@
         private InlineSuggestionsRequest mPendingInlineSuggestionsRequest;
         @GuardedBy("mLock")
         private FillRequest mPendingFillRequest;
+        @GuardedBy("mLock")
+        private FillRequest mLastFillRequest;
 
         @Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState,
                 boolean isInlineRequest) {
@@ -473,6 +507,7 @@
             mPendingInlineSuggestionsRequest = inlineRequest;
         }
 
+        @GuardedBy("mLock")
         void maybeRequestFillFromServiceLocked() {
             if (mPendingFillRequest == null) {
                 return;
@@ -490,9 +525,12 @@
                     mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
                             mPendingFillRequest.getFillContexts(),
                             mPendingFillRequest.getClientState(),
-                            mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
+                            mPendingFillRequest.getFlags(),
+                            mPendingInlineSuggestionsRequest,
+                            mPendingFillRequest.getDelayedFillIntentSender());
                 }
             }
+            mLastFillRequest = mPendingFillRequest;
 
             mRemoteFillService.onFillRequest(mPendingFillRequest);
             mPendingInlineSuggestionsRequest = null;
@@ -594,8 +632,12 @@
 
                 final ArrayList<FillContext> contexts =
                         mergePreviousSessionLocked(/* forSave= */ false);
+                mDelayedFillPendingIntent = createPendingIntent(requestId);
                 request = new FillRequest(requestId, contexts, mClientState, flags,
-                        /*inlineSuggestionsRequest=*/null);
+                        /*inlineSuggestionsRequest=*/ null,
+                        /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null
+                            ? null
+                            : mDelayedFillPendingIntent.getIntentSender());
 
                 mPendingFillRequest = request;
                 maybeRequestFillFromServiceLocked();
@@ -610,7 +652,70 @@
         public void onHandleAssistScreenshot(Bitmap screenshot) {
             // Do nothing
         }
-    };
+
+        @GuardedBy("mLock")
+        void processDelayedFillLocked(int requestId, FillResponse response) {
+            if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) {
+                Slog.v(TAG, "processDelayedFillLocked: "
+                        + "calling onFillRequestSuccess with new response");
+                onFillRequestSuccess(requestId, response,
+                        mService.getServicePackageName(), mLastFillRequest.getFlags());
+            }
+        }
+    }
+
+    /** Creates {@link PendingIntent} for autofill service to send a delayed fill. */
+    private PendingIntent createPendingIntent(int requestId) {
+        Slog.d(TAG, "createPendingIntent for request " + requestId);
+        PendingIntent pendingIntent;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android")
+                    .putExtra(EXTRA_REQUEST_ID, requestId);
+            pendingIntent = PendingIntent.getBroadcast(
+                    mContext, this.id, intent,
+                    PendingIntent.FLAG_MUTABLE
+                        | PendingIntent.FLAG_ONE_SHOT
+                        | PendingIntent.FLAG_CANCEL_CURRENT);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return pendingIntent;
+    }
+
+    @GuardedBy("mLock")
+    private void clearPendingIntentLocked() {
+        Slog.d(TAG, "clearPendingIntentLocked");
+        if (mDelayedFillPendingIntent == null) {
+            return;
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mDelayedFillPendingIntent.cancel();
+            mDelayedFillPendingIntent = null;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void registerDelayedFillBroadcastLocked() {
+        if (!mDelayedFillBroadcastReceiverRegistered) {
+            Slog.v(TAG, "registerDelayedFillBroadcastLocked()");
+            IntentFilter intentFilter = new IntentFilter(ACTION_DELAYED_FILL);
+            mContext.registerReceiver(mDelayedFillBroadcastReceiver, intentFilter);
+            mDelayedFillBroadcastReceiverRegistered = true;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void unregisterDelayedFillBroadcastLocked() {
+        if (mDelayedFillBroadcastReceiverRegistered) {
+            Slog.v(TAG, "unregisterDelayedFillBroadcastLocked()");
+            mContext.unregisterReceiver(mDelayedFillBroadcastReceiver);
+            mDelayedFillBroadcastReceiverRegistered = false;
+        }
+    }
 
     /**
      * Returns the ids of all entries in {@link #mViewStates} in the same order.
@@ -964,6 +1069,7 @@
         mHasCallback = hasCallback;
         mUiLatencyHistory = uiLatencyHistory;
         mWtfHistory = wtfHistory;
+        mContext = context;
         mComponentName = componentName;
         mCompatMode = compatMode;
         mSessionState = STATE_ACTIVE;
@@ -1096,6 +1202,12 @@
                 processNullResponseLocked(requestId, requestFlags);
                 return;
             }
+
+            final int flags = response.getFlags();
+            if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) {
+                Slog.v(TAG, "Service requested to wait for delayed fill response.");
+                registerDelayedFillBroadcastLocked();
+            }
         }
 
         mService.setLastResponse(id, response);
@@ -1206,6 +1318,7 @@
             @Nullable CharSequence message) {
         boolean showMessage = !TextUtils.isEmpty(message);
         synchronized (mLock) {
+            unregisterDelayedFillBroadcastLocked();
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
                         + ") rejected - session: " + id + " destroyed");
@@ -1522,7 +1635,7 @@
                 Slog.e(TAG, "Error sending input show up notification", e);
             }
         }
-        synchronized (Session.this.mLock) {
+        synchronized (mLock) {
             // stop to show fill dialog
             mSessionFlags.mFillDialogDisabled = true;
         }
@@ -3259,7 +3372,7 @@
     private boolean isFillDialogUiEnabled() {
         // TODO read from Settings or somewhere
         final boolean isSettingsEnabledFillDialog = true;
-        synchronized (Session.this.mLock) {
+        synchronized (mLock) {
             return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled;
         }
     }
@@ -3530,6 +3643,7 @@
 
     @GuardedBy("mLock")
     private void processNullResponseLocked(int requestId, int flags) {
+        unregisterDelayedFillBroadcastLocked();
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
         }
@@ -3744,6 +3858,11 @@
         // only if handling the current response requires it.
         mUi.hideAll(this);
 
+        if ((newResponse.getFlags() & FillResponse.FLAG_DELAY_FILL) == 0) {
+            Slog.d(TAG, "Service did not request to wait for delayed fill response.");
+            unregisterDelayedFillBroadcastLocked();
+        }
+
         final int requestId = newResponse.getRequestId();
         if (sVerbose) {
             Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
@@ -4296,6 +4415,9 @@
             return null;
         }
 
+        clearPendingIntentLocked();
+        unregisterDelayedFillBroadcastLocked();
+
         unlinkClientVultureLocked();
         mUi.destroyAll(mPendingSaveUi, this, true);
         mUi.clearCallback(this);
diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
index f1d98f0..0509e0c 100644
--- a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
+++ b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
@@ -36,7 +36,6 @@
  * will be killed if association/role are revoked.
  */
 public class AssociationCleanUpService extends JobService {
-    private static final String TAG = LOG_TAG + ".AssociationCleanUpService";
     private static final int JOB_ID = AssociationCleanUpService.class.hashCode();
     private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day
     private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService(
@@ -56,7 +55,7 @@
 
     @Override
     public boolean onStopJob(final JobParameters params) {
-        Slog.d(TAG, "Association cleanup job stopped; id=" + params.getJobId()
+        Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId()
                 + ", reason="
                 + JobParameters.getInternalReasonCodeDescription(
                 params.getInternalStopReasonCode()));
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index cda554e..21a677b8 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
 import android.net.MacAddress;
@@ -52,9 +53,10 @@
  * Other system component (both inside and outside if the com.android.server.companion package)
  * should use public {@link AssociationStore} interface.
  */
+@SuppressLint("LongLogTag")
 class AssociationStoreImpl implements AssociationStore {
     private static final boolean DEBUG = false;
-    private static final String TAG = "AssociationStore";
+    private static final String TAG = "CompanionDevice_AssociationStore";
 
     private final Object mLock = new Object();
 
@@ -125,7 +127,7 @@
             // Update the MacAddress-to-List<Association> map if needed.
             final MacAddress updatedAddress = updated.getDeviceMacAddress();
             final MacAddress currentAddress = current.getDeviceMacAddress();
-            macAddressChanged = Objects.equals(currentAddress, updatedAddress);
+            macAddressChanged = !Objects.equals(currentAddress, updatedAddress);
             if (macAddressChanged) {
                 if (currentAddress != null) {
                     mAddressMap.get(currentAddress).remove(id);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index cfd3798..c3ab2a7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1255,7 +1255,7 @@
         }
 
         @Override
-        public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {
+        public void onDeviceDisconnected(BluetoothDevice device, int reason) {
             Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") "
                     + BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason));
             CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index f2e66077..fd13085 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -16,16 +16,17 @@
 
 package com.android.server.companion;
 
-import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
-
 import android.companion.AssociationInfo;
+import android.os.ShellCommand;
 import android.util.Log;
 import android.util.Slog;
 
 import java.io.PrintWriter;
 import java.util.List;
 
-class CompanionDeviceShellCommand extends android.os.ShellCommand {
+class CompanionDeviceShellCommand extends ShellCommand {
+    private static final String TAG = "CompanionDevice_ShellCommand";
+
     private final CompanionDeviceManagerService mService;
     private final AssociationStore mAssociationStore;
 
@@ -84,7 +85,7 @@
             }
             return 0;
         } catch (Throwable t) {
-            Slog.e(LOG_TAG, "Error running a command: $ " + cmd, t);
+            Slog.e(TAG, "Error running a command: $ " + cmd, t);
             getErrPrintWriter().println(Log.getStackTraceString(t));
             return 1;
         }
diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/DataStoreUtils.java
index 6055a81..8ac741a 100644
--- a/services/companion/java/com/android/server/companion/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/DataStoreUtils.java
@@ -25,7 +25,7 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 
-import com.android.internal.util.FunctionalUtils;
+import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -34,8 +34,7 @@
 import java.io.FileOutputStream;
 
 final class DataStoreUtils {
-
-    private static final String LOG_TAG = DataStoreUtils.class.getSimpleName();
+    private static final String TAG = "CompanionDevice_DataStoreUtils";
 
     static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
             throws XmlPullParserException {
@@ -71,12 +70,12 @@
      * Writing to file could fail, for example, if the user has been recently removed and so was
      * their DE (/data/system_de/<user-id>/) directory.
      */
-    static void writeToFileSafely(@NonNull AtomicFile file,
-            @NonNull FunctionalUtils.ThrowingConsumer<FileOutputStream> consumer) {
+    static void writeToFileSafely(
+            @NonNull AtomicFile file, @NonNull ThrowingConsumer<FileOutputStream> consumer) {
         try {
             file.write(consumer);
         } catch (Exception e) {
-            Slog.e(LOG_TAG, "Error while writing to file " + file, e);
+            Slog.e(TAG, "Error while writing to file " + file, e);
         }
     }
 
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index da33b44..d0cc122 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -32,6 +32,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
 import android.content.pm.UserInfo;
@@ -39,6 +40,7 @@
 import android.os.Environment;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TypedXmlPullParser;
@@ -146,8 +148,9 @@
  * </state>
  * }</pre>
  */
+@SuppressLint("LongLogTag")
 final class PersistentDataStore {
-    private static final String LOG_TAG = CompanionDeviceManagerService.LOG_TAG + ".DataStore";
+    private static final String TAG = "CompanionDevice_PersistentDataStore";
     private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG;
 
     private static final int CURRENT_PERSISTENCE_VERSION = 1;
@@ -208,10 +211,9 @@
     void readStateForUser(@UserIdInt int userId,
             @NonNull Collection<AssociationInfo> associationsOut,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
-        Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk");
-
+        Slog.i(TAG, "Reading associations for user " + userId + " from disk");
         final AtomicFile file = getStorageFileForUser(userId);
-        if (DEBUG) Slog.d(LOG_TAG, "  > File=" + file.getBaseFile().getPath());
+        if (DEBUG) Log.d(TAG, "  > File=" + file.getBaseFile().getPath());
 
         // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
         // accesses to the file on the file system using this AtomicFile object.
@@ -220,12 +222,12 @@
             final AtomicFile readFrom;
             final String rootTag;
             if (!file.getBaseFile().exists()) {
-                if (DEBUG) Slog.d(LOG_TAG, "  > File does not exist -> Try to read legacy file");
+                if (DEBUG) Log.d(TAG, "  > File does not exist -> Try to read legacy file");
 
                 legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
-                if (DEBUG) Slog.d(LOG_TAG, "  > Legacy file=" + legacyBaseFile.getPath());
+                if (DEBUG) Log.d(TAG, "  > Legacy file=" + legacyBaseFile.getPath());
                 if (!legacyBaseFile.exists()) {
-                    if (DEBUG) Slog.d(LOG_TAG, "  > Legacy file does not exist -> Abort");
+                    if (DEBUG) Log.d(TAG, "  > Legacy file does not exist -> Abort");
                     return;
                 }
 
@@ -236,13 +238,13 @@
                 rootTag = XML_TAG_STATE;
             }
 
-            if (DEBUG) Slog.d(LOG_TAG, "  > Reading associations...");
+            if (DEBUG) Log.d(TAG, "  > Reading associations...");
             final int version = readStateFromFileLocked(userId, readFrom, rootTag,
                     associationsOut, previouslyUsedIdsPerPackageOut);
             if (DEBUG) {
-                Slog.d(LOG_TAG, "  > Done reading: " + associationsOut);
+                Log.d(TAG, "  > Done reading: " + associationsOut);
                 if (version < CURRENT_PERSISTENCE_VERSION) {
-                    Slog.d(LOG_TAG, "  > File used old format: v." + version + " -> Re-write");
+                    Log.d(TAG, "  > File used old format: v." + version + " -> Re-write");
                 }
             }
 
@@ -250,13 +252,13 @@
                 // The data is either in the legacy file or in the legacy format, or both.
                 // Save the data to right file in using the current format.
                 if (DEBUG) {
-                    Slog.d(LOG_TAG, "  > Writing the data to " + file.getBaseFile().getPath());
+                    Log.d(TAG, "  > Writing the data to " + file.getBaseFile().getPath());
                 }
                 persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
 
                 if (legacyBaseFile != null) {
                     // We saved the data to the right file, can delete the old file now.
-                    if (DEBUG) Slog.d(LOG_TAG, "  > Deleting legacy file");
+                    if (DEBUG) Log.d(TAG, "  > Deleting legacy file");
                     legacyBaseFile.delete();
                 }
             }
@@ -273,11 +275,11 @@
     void persistStateForUser(@UserIdInt int userId,
             @NonNull Collection<AssociationInfo> associations,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
-        Slog.i(LOG_TAG, "Writing associations for user " + userId + " to disk");
-        if (DEBUG) Slog.d(LOG_TAG, "  > " + associations);
+        Slog.i(TAG, "Writing associations for user " + userId + " to disk");
+        if (DEBUG) Slog.d(TAG, "  > " + associations);
 
         final AtomicFile file = getStorageFileForUser(userId);
-        if (DEBUG) Slog.d(LOG_TAG, "  > File=" + file.getBaseFile().getPath());
+        if (DEBUG) Log.d(TAG, "  > File=" + file.getBaseFile().getPath());
         // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
         // accesses to the file on the file system using this AtomicFile object.
         synchronized (file) {
@@ -312,7 +314,7 @@
             }
             return version;
         } catch (XmlPullParserException | IOException e) {
-            Slog.e(LOG_TAG, "Error while reading associations file", e);
+            Slog.e(TAG, "Error while reading associations file", e);
             return -1;
         }
     }
@@ -528,7 +530,7 @@
             associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
                     displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected);
         } catch (Exception e) {
-            if (DEBUG) Slog.w(LOG_TAG, "Could not create AssociationInfo", e);
+            if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
         }
         return associationInfo;
     }
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
index 76340fc..904283f 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -19,20 +19,23 @@
 import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;
 
 import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.role.RoleManager;
 import android.companion.AssociationInfo;
 import android.content.Context;
 import android.os.UserHandle;
+import android.util.Log;
 import android.util.Slog;
 
 import java.util.List;
 
 /** Utility methods for accessing {@link RoleManager} APIs. */
+@SuppressLint("LongLogTag")
 final class RolesUtils {
+    private static final String TAG = CompanionDeviceManagerService.LOG_TAG;
 
     static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
             @NonNull String packageName, @NonNull String role) {
@@ -45,7 +48,7 @@
     static void addRoleHolderForAssociation(
             @NonNull Context context, @NonNull AssociationInfo associationInfo) {
         if (DEBUG) {
-            Slog.d(LOG_TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
+            Log.d(TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
         }
 
         final String deviceProfile = associationInfo.getDeviceProfile();
@@ -61,7 +64,7 @@
                 MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
                 success -> {
                     if (!success) {
-                        Slog.e(LOG_TAG, "Failed to add u" + userId + "\\" + packageName
+                        Slog.e(TAG, "Failed to add u" + userId + "\\" + packageName
                                 + " to the list of " + deviceProfile + " holders.");
                     }
                 });
@@ -70,7 +73,7 @@
     static void removeRoleHolderForAssociation(
             @NonNull Context context, @NonNull AssociationInfo associationInfo) {
         if (DEBUG) {
-            Slog.d(LOG_TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
+            Log.d(TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
         }
 
         final String deviceProfile = associationInfo.getDeviceProfile();
@@ -86,7 +89,7 @@
                 MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
                 success -> {
                     if (!success) {
-                        Slog.e(LOG_TAG, "Failed to remove u" + userId + "\\" + packageName
+                        Slog.e(TAG, "Failed to remove u" + userId + "\\" + packageName
                                 + " from the list of " + deviceProfile + " holders.");
                     }
                 });
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 0eb6b8d..a771e7b 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -21,8 +21,6 @@
 import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
 import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
 import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
 import static android.bluetooth.BluetoothAdapter.nameForState;
 import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
 import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
@@ -35,6 +33,7 @@
 import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
 import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
 
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
 import static com.android.server.companion.presence.Utils.btDeviceToString;
 
 import static java.util.Objects.requireNonNull;
@@ -72,7 +71,6 @@
 
 @SuppressLint("LongLogTag")
 class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
-    private static final boolean DEBUG = false;
     private static final String TAG = "CompanionDevice_PresenceMonitor_BLE";
 
     /**
@@ -156,7 +154,7 @@
     private void checkBleState() {
         enforceInitialized();
 
-        final boolean bleAvailable = isBleAvailable();
+        final boolean bleAvailable = mBtAdapter.isLeEnabled();
         if (DEBUG) {
             Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
         }
@@ -183,16 +181,6 @@
         }
     }
 
-    /**
-     * A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private
-     * access level, so it's not accessible from here.
-     */
-    private boolean isBleAvailable() {
-        final int state = mBtAdapter.getLeState();
-        if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state));
-        return state == STATE_ON || state == STATE_BLE_ON;
-    }
-
     @MainThread
     private void startScan() {
         enforceInitialized();
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index dbe866b..1ba198a 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -16,6 +16,7 @@
 
 package com.android.server.companion.presence;
 
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
 import static com.android.server.companion.presence.Utils.btDeviceToString;
 
 import android.annotation.NonNull;
@@ -39,7 +40,6 @@
 class BluetoothCompanionDeviceConnectionListener
         extends BluetoothAdapter.BluetoothConnectionCallback
         implements AssociationStore.OnChangeListener {
-    private static final boolean DEBUG = false;
     private static final String TAG = "CompanionDevice_PresenceMonitor_BT";
 
     interface Callback {
@@ -91,7 +91,7 @@
      */
     @Override
     public void onDeviceDisconnected(@NonNull BluetoothDevice device,
-            @DisconnectReason int reason) {
+            int reason) {
         if (DEBUG) {
             Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
             Log.d(TAG, "  reason=" + disconnectReasonText(reason));
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
new file mode 100644
index 0000000..6371b25
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.companion.presence;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.server.companion.AssociationStore;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class responsible for monitoring companion devices' "presence" status (i.e.
+ * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
+ *
+ * <p>
+ * Should only be used by
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * to which it provides the following API:
+ * <ul>
+ * <li> {@link #onSelfManagedDeviceConnected(int)}
+ * <li> {@link #onSelfManagedDeviceDisconnected(int)}
+ * <li> {@link #isDevicePresent(int)}
+ * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
+ * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
+ * </ul>
+ */
+@SuppressLint("LongLogTag")
+public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener,
+        BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
+    static final boolean DEBUG = false;
+    private static final String TAG = "CompanionDevice_PresenceMonitor";
+
+    /** Callback for notifying about changes to status of companion devices. */
+    public interface Callback {
+        /** Invoked when companion device is found nearby or connects. */
+        void onDeviceAppeared(int associationId);
+
+        /** Invoked when a companion device no longer seen nearby or disconnects. */
+        void onDeviceDisappeared(int associationId);
+    }
+
+    private final @NonNull AssociationStore mAssociationStore;
+    private final @NonNull Callback mCallback;
+    private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+    private final @NonNull BleCompanionDeviceScanner mBleScanner;
+
+    // NOTE: Same association may appear in more than one of the following sets at the same time.
+    // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
+    // companion applications, while at the same be connected via BT, or detected nearby by BLE
+    // scanner)
+    private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
+    private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
+    private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+
+    public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
+            @NonNull Callback callback) {
+        mAssociationStore = associationStore;
+        mCallback = callback;
+
+        mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(associationStore,
+                /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+        mBleScanner = new BleCompanionDeviceScanner(associationStore,
+                /* BleCompanionDeviceScanner.Callback */ this);
+    }
+
+    /** Initialize {@link CompanionDevicePresenceMonitor} */
+    public void init(Context context) {
+        if (DEBUG) Log.i(TAG, "init()");
+
+        final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (btAdapter != null) {
+            mBtConnectionListener.init(btAdapter);
+            mBleScanner.init(context, btAdapter);
+        } else {
+            Log.w(TAG, "BluetoothAdapter is NOT available.");
+        }
+
+        mAssociationStore.registerListener(this);
+    }
+
+    /**
+     * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
+     *         or devices is connected (for Bluetooth); or reported (by the application) to be
+     *         nearby (for "self-managed" associations).
+     */
+    public boolean isDevicePresent(int associationId) {
+        return mReportedSelfManagedDevices.contains(associationId)
+                || mConnectedBtDevices.contains(associationId)
+                || mNearbyBleDevices.contains(associationId);
+    }
+
+    /**
+     * Marks a "self-managed" device as connected.
+     *
+     * <p>
+     * Must ONLY be invoked by the
+     * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+     * when an application invokes
+     * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
+     */
+    public void onSelfManagedDeviceConnected(int associationId) {
+        onDevicePresent(mReportedSelfManagedDevices, associationId, "application-reported");
+    }
+
+    /**
+     * Marks a "self-managed" device as disconnected.
+     *
+     * <p>
+     * Must ONLY be invoked by the
+     * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+     * when an application invokes
+     * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
+     */
+    public void onSelfManagedDeviceDisconnected(int associationId) {
+        onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported");
+    }
+
+    @Override
+    public void onBluetoothCompanionDeviceConnected(int associationId) {
+        onDevicePresent(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
+    }
+
+    @Override
+    public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+        onDeviceGone(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
+    }
+
+    @Override
+    public void onBleCompanionDeviceFound(int associationId) {
+        onDevicePresent(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
+    }
+
+    @Override
+    public void onBleCompanionDeviceLost(int associationId) {
+        onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
+    }
+
+    private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
+            int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
+        if (DEBUG) {
+            Log.i(TAG, "onDevice_Present() id=" + newDeviceAssociationId
+                    + ", source=" + sourceLoggingTag);
+            Log.d(TAG, "  > association="
+                    + mAssociationStore.getAssociationById(newDeviceAssociationId));
+        }
+
+        final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId);
+        if (DEBUG && alreadyPresent) Log.i(TAG, "Device is already present.");
+
+        final boolean added = presentDevicesForSource.add(newDeviceAssociationId);
+        if (DEBUG && !added) {
+            Log.w(TAG, "Association with id " + newDeviceAssociationId + " is ALREADY reported as "
+                    + "present by this source (" + sourceLoggingTag + ")");
+        }
+
+        if (alreadyPresent) return;
+
+        mCallback.onDeviceAppeared(newDeviceAssociationId);
+    }
+
+    private void onDeviceGone(@NonNull Set<Integer> presentDevicesForSource,
+            int goneDeviceAssociationId, @NonNull String sourceLoggingTag) {
+        if (DEBUG) {
+            Log.i(TAG, "onDevice_Gone() id=" + goneDeviceAssociationId
+                    + ", source=" + sourceLoggingTag);
+            Log.d(TAG, "  > association="
+                    + mAssociationStore.getAssociationById(goneDeviceAssociationId));
+        }
+
+        final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId);
+        if (!removed) {
+            if (DEBUG) {
+                Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
+                        + "as present by this source (" + sourceLoggingTag + ")");
+            }
+            return;
+        }
+
+        final boolean stillPresent = isDevicePresent(goneDeviceAssociationId);
+        if (stillPresent) {
+            if (DEBUG) Log.i(TAG, "  Device is still present.");
+            return;
+        }
+
+        mCallback.onDeviceDisappeared(goneDeviceAssociationId);
+    }
+
+    /**
+     * Implements
+     * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
+     */
+    @Override
+    public void onAssociationRemoved(@NonNull AssociationInfo association) {
+        final int id = association.getId();
+        if (DEBUG) {
+            Log.i(TAG, "onAssociationRemoved() id=" + id);
+            Log.d(TAG, "  > association=" + association);
+        }
+
+        mConnectedBtDevices.remove(id);
+        mNearbyBleDevices.remove(id);
+        mReportedSelfManagedDevices.remove(id);
+
+        // Do NOT call mCallback.onDeviceDisappeared()!
+        // CompanionDeviceManagerService will know that the association is removed, and will do
+        // what's needed.
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 6c56e2f..e6bfd1f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -18,8 +18,11 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.StringDef;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
@@ -48,6 +51,20 @@
 
     private static final String TAG = "VirtualInputController";
 
+    private static final AtomicLong sNextPhysId = new AtomicLong(1);
+
+    static final String PHYS_TYPE_KEYBOARD = "Keyboard";
+    static final String PHYS_TYPE_MOUSE = "Mouse";
+    static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
+    @StringDef(prefix = { "PHYS_TYPE_" }, value = {
+            PHYS_TYPE_KEYBOARD,
+            PHYS_TYPE_MOUSE,
+            PHYS_TYPE_TOUCHSCREEN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PhysType {
+    }
+
     private final Object mLock;
 
     /* Token -> file descriptor associations. */
@@ -56,6 +73,8 @@
     final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
 
     private final NativeWrapper mNativeWrapper;
+    private final DisplayManagerInternal mDisplayManagerInternal;
+    private final InputManagerInternal mInputManagerInternal;
 
     /**
      * Because the pointer is a singleton, it can only be targeted at one display at a time. Because
@@ -73,6 +92,8 @@
         mLock = lock;
         mNativeWrapper = nativeWrapper;
         mActivePointerDisplayId = Display.INVALID_DISPLAY;
+        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
     }
 
     void close() {
@@ -90,7 +111,9 @@
             int productId,
             @NonNull IBinder deviceToken,
             int displayId) {
-        final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId);
+        final String phys = createPhys(PHYS_TYPE_KEYBOARD);
+        setUniqueIdAssociation(displayId, phys);
+        final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
         if (fd < 0) {
             throw new RuntimeException(
                     "A native error occurred when creating keyboard: " + -fd);
@@ -99,7 +122,7 @@
         synchronized (mLock) {
             mInputDeviceDescriptors.put(deviceToken,
                     new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_KEYBOARD, displayId));
+                            InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
         }
         try {
             deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -114,7 +137,9 @@
             int productId,
             @NonNull IBinder deviceToken,
             int displayId) {
-        final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId);
+        final String phys = createPhys(PHYS_TYPE_MOUSE);
+        setUniqueIdAssociation(displayId, phys);
+        final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
         if (fd < 0) {
             throw new RuntimeException(
                     "A native error occurred when creating mouse: " + -fd);
@@ -123,11 +148,9 @@
         synchronized (mLock) {
             mInputDeviceDescriptors.put(deviceToken,
                     new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_MOUSE, displayId));
-            final InputManagerInternal inputManagerInternal =
-                    LocalServices.getService(InputManagerInternal.class);
-            inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
-            inputManagerInternal.setPointerAcceleration(1);
+                            InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
+            mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+            mInputManagerInternal.setPointerAcceleration(1);
             mActivePointerDisplayId = displayId;
         }
         try {
@@ -144,7 +167,9 @@
             @NonNull IBinder deviceToken,
             int displayId,
             @NonNull Point screenSize) {
-        final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+        final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
+        setUniqueIdAssociation(displayId, phys);
+        final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
                 screenSize.y, screenSize.x);
         if (fd < 0) {
             throw new RuntimeException(
@@ -154,7 +179,7 @@
         synchronized (mLock) {
             mInputDeviceDescriptors.put(deviceToken,
                     new InputDeviceDescriptor(fd, binderDeathRecipient,
-                            InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId));
+                            InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
         }
         try {
             deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -174,6 +199,7 @@
             }
             token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
             mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+            InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
 
             // Reset values to the default if all virtual mice are unregistered, or set display
             // id if there's another mouse (choose the most recent).
@@ -197,9 +223,7 @@
             }
         }
         if (mostRecentlyCreatedMouse != null) {
-            final InputManagerInternal inputManagerInternal =
-                    LocalServices.getService(InputManagerInternal.class);
-            inputManagerInternal.setVirtualMousePointerDisplayId(
+            mInputManagerInternal.setVirtualMousePointerDisplayId(
                     mostRecentlyCreatedMouse.getDisplayId());
             mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId();
         } else {
@@ -209,14 +233,21 @@
     }
 
     private void resetMouseValuesLocked() {
-        final InputManagerInternal inputManagerInternal =
-                LocalServices.getService(InputManagerInternal.class);
-        inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
-        inputManagerInternal.setPointerAcceleration(
+        mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+        mInputManagerInternal.setPointerAcceleration(
                 IInputConstants.DEFAULT_POINTER_ACCELERATION);
         mActivePointerDisplayId = Display.INVALID_DISPLAY;
     }
 
+    private static String createPhys(@PhysType String type) {
+        return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement());
+    }
+
+    private void setUniqueIdAssociation(int displayId, String phys) {
+        final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
+        InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+    }
+
     boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
         synchronized (mLock) {
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
@@ -321,17 +352,18 @@
                 fout.println("          creationOrder: "
                         + inputDeviceDescriptor.getCreationOrderNumber());
                 fout.println("          type: " + inputDeviceDescriptor.getType());
+                fout.println("          phys: " + inputDeviceDescriptor.getPhys());
             }
             fout.println("      Active mouse display id: " + mActivePointerDisplayId);
         }
     }
 
     private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
-            int productId);
-    private static native int nativeOpenUinputMouse(String deviceName, int vendorId,
-            int productId);
+            int productId, String phys);
+    private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
+            String phys);
     private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
-            int productId, int height, int width);
+            int productId, String phys, int height, int width);
     private static native boolean nativeCloseUinput(int fd);
     private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
     private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
@@ -345,20 +377,18 @@
     /** Wrapper around the static native methods for tests. */
     @VisibleForTesting
     protected static class NativeWrapper {
-        public int openUinputKeyboard(String deviceName, int vendorId, int productId) {
-            return nativeOpenUinputKeyboard(deviceName, vendorId,
-                    productId);
+        public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) {
+            return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys);
         }
 
-        public int openUinputMouse(String deviceName, int vendorId, int productId) {
-            return nativeOpenUinputMouse(deviceName, vendorId,
-                    productId);
+        public int openUinputMouse(String deviceName, int vendorId, int productId, String phys) {
+            return nativeOpenUinputMouse(deviceName, vendorId, productId, phys);
         }
 
-        public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height,
-                int width) {
-            return nativeOpenUinputTouchscreen(deviceName, vendorId,
-                    productId, height, width);
+        public int openUinputTouchscreen(String deviceName, int vendorId,
+                int productId, String phys, int height, int width) {
+            return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height,
+                    width);
         }
 
         public boolean closeUinput(int fd) {
@@ -410,15 +440,17 @@
         private final IBinder.DeathRecipient mDeathRecipient;
         private final @Type int mType;
         private final int mDisplayId;
+        private final String mPhys;
         // Monotonically increasing number; devices with lower numbers were created earlier.
         private final long mCreationOrderNumber;
 
-        InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient,
-                @Type int type, int displayId) {
+        InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type,
+                int displayId, String phys) {
             mFd = fd;
             mDeathRecipient = deathRecipient;
             mType = type;
             mDisplayId = displayId;
+            mPhys = phys;
             mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
         }
 
@@ -445,6 +477,10 @@
         public long getCreationOrderNumber() {
             return mCreationOrderNumber;
         }
+
+        public String getPhys() {
+            return mPhys;
+        }
     }
 
     private final class BinderDeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1106fe7..561009f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -134,6 +134,7 @@
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
         "service-permission.stubs.system_server",
+        "service-supplementalprocess.stubs.system_server",
     ],
 
     required: [
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 435d294..a35aa7c 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
 import android.content.ComponentName;
 import android.content.LocusId;
@@ -375,10 +376,11 @@
      *                           to this broadcast.
      * @param timestampMs time (in millis) when the broadcast was dispatched, in
      *                    {@link SystemClock#elapsedRealtime()} timebase.
+     * @param targetUidProcState process state of the uid that the broadcast is targeted to.
      */
     public abstract void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
             @NonNull UserHandle targetUser, long idForResponseEvent,
-            @ElapsedRealtimeLong long timestampMs);
+            @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState);
 
     /**
      * Reports a notification posted event to the UsageStatsManager.
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index db510cb..7714dbc 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -33,6 +33,7 @@
 import android.util.Slog;
 
 import com.android.internal.os.IBinaryTransparencyService;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -373,6 +374,7 @@
     private void getVBMetaDigestInformation() {
         mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE);
         Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest));
+        FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
     }
 
     @NonNull
@@ -437,6 +439,13 @@
                     } else {
                         mBinaryHashes.put(packageName, sha256digest);
                     }
+
+                    if (packageInfo.isApex) {
+                        FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
+                                packageInfo.packageName,
+                                packageInfo.getLongVersionCode(),
+                                mBinaryHashes.get(packageInfo.packageName));
+                    }
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 Slog.e(TAG, "Could not find package with name " + packageName);
@@ -466,6 +475,8 @@
             } else {
                 mBinaryHashes.put(packageInfo.packageName, sha256digest);
             }
+            FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, packageInfo.packageName,
+                    packageInfo.getLongVersionCode(), mBinaryHashes.get(packageInfo.packageName));
             Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName,
                     packageInfo.lastUpdateTime));
             mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index e5a7b4e..8a6b54f 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,7 +19,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -32,15 +31,21 @@
 import android.os.UEventObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
+import com.android.server.ExtconUEventObserver.ExtconInfo;
 
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * DockObserver monitors for a docking station.
@@ -48,9 +53,6 @@
 final class DockObserver extends SystemService {
     private static final String TAG = "DockObserver";
 
-    private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
-    private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
-
     private static final int MSG_DOCK_STATE_CHANGED = 0;
 
     private final PowerManager mPowerManager;
@@ -69,6 +71,92 @@
 
     private final boolean mAllowTheaterModeWakeFromDock;
 
+    private final List<ExtconStateConfig> mExtconStateConfigs;
+
+    static final class ExtconStateProvider {
+        private final Map<String, String> mState;
+
+        ExtconStateProvider(Map<String, String> state) {
+            mState = state;
+        }
+
+        String getValue(String key) {
+            return mState.get(key);
+        }
+
+
+        static ExtconStateProvider fromString(String stateString) {
+            Map<String, String> states = new HashMap<>();
+            String[] lines = stateString.split("\n");
+            for (String line : lines) {
+                String[] fields = line.split("=");
+                if (fields.length == 2) {
+                    states.put(fields[0], fields[1]);
+                } else {
+                    Slog.e(TAG, "Invalid line: " + line);
+                }
+            }
+            return new ExtconStateProvider(states);
+        }
+
+        static ExtconStateProvider fromFile(String stateFilePath) {
+            char[] buffer = new char[1024];
+            try (FileReader file = new FileReader(stateFilePath)) {
+                int len = file.read(buffer, 0, 1024);
+                String stateString = (new String(buffer, 0, len)).trim();
+                return ExtconStateProvider.fromString(stateString);
+            } catch (FileNotFoundException e) {
+                Slog.w(TAG, "No state file found at: " + stateFilePath);
+                return new ExtconStateProvider(new HashMap<>());
+            } catch (Exception e) {
+                Slog.e(TAG, "" , e);
+                return new ExtconStateProvider(new HashMap<>());
+            }
+        }
+    }
+
+    /**
+     * Represents a mapping from extcon state to EXTRA_DOCK_STATE value. Each
+     * instance corresponds to an entry in config_dockExtconStateMapping.
+     */
+    private static final class ExtconStateConfig {
+
+        // The EXTRA_DOCK_STATE that will be used if the extcon key-value pairs match
+        public final int extraStateValue;
+
+        // A list of key-value pairs that must be present in the extcon state for a match
+        // to be considered. An empty list is considered a matching wildcard.
+        public final List<Pair<String, String>> keyValuePairs = new ArrayList<>();
+
+        ExtconStateConfig(int extraStateValue) {
+            this.extraStateValue = extraStateValue;
+        }
+    }
+
+    private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
+        String[] rows = context.getResources().getStringArray(
+            com.android.internal.R.array.config_dockExtconStateMapping);
+        try {
+            ArrayList<ExtconStateConfig> configs = new ArrayList<>();
+            for (String row : rows) {
+                String[] rowFields = row.split(",");
+                ExtconStateConfig config = new ExtconStateConfig(Integer.parseInt(rowFields[0]));
+                for (int i = 1; i < rowFields.length; i++) {
+                    String[] keyValueFields = rowFields[i].split("=");
+                    if (keyValueFields.length != 2) {
+                        throw new IllegalArgumentException("Invalid key-value: " + rowFields[i]);
+                    }
+                    config.keyValuePairs.add(Pair.create(keyValueFields[0], keyValueFields[1]));
+                }
+                configs.add(config);
+            }
+            return configs;
+        } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
+            Slog.e(TAG, "Could not parse extcon state config", e);
+            return new ArrayList<>();
+        }
+    }
+
     public DockObserver(Context context) {
         super(context);
 
@@ -77,9 +165,25 @@
         mAllowTheaterModeWakeFromDock = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
 
-        init();  // set initial status
+        mExtconStateConfigs = loadExtconStateConfigs(context);
 
-        mObserver.startObserving(DOCK_UEVENT_MATCH);
+        List<ExtconInfo> infos = ExtconInfo.getExtconInfoForTypes(new String[] {
+                ExtconInfo.EXTCON_DOCK
+        });
+
+        if (!infos.isEmpty()) {
+            ExtconInfo info = infos.get(0);
+            Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath()
+                        + ", statePath: " + info.getStatePath());
+
+            // set initial status
+            setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath()));
+            mPreviousDockState = mActualDockState;
+
+            mExtconUEventObserver.startObserving(info);
+        } else {
+            Slog.i(TAG, "No extcon dock device found in this kernel.");
+        }
     }
 
     @Override
@@ -101,26 +205,6 @@
         }
     }
 
-    private void init() {
-        synchronized (mLock) {
-            try {
-                char[] buffer = new char[1024];
-                FileReader file = new FileReader(DOCK_STATE_PATH);
-                try {
-                    int len = file.read(buffer, 0, 1024);
-                    setActualDockStateLocked(Integer.parseInt((new String(buffer, 0, len)).trim()));
-                    mPreviousDockState = mActualDockState;
-                } finally {
-                    file.close();
-                }
-            } catch (FileNotFoundException e) {
-                Slog.w(TAG, "This kernel does not have dock station support");
-            } catch (Exception e) {
-                Slog.e(TAG, "" , e);
-            }
-        }
-    }
-
     private void setActualDockStateLocked(int newState) {
         mActualDockState = newState;
         if (!mUpdatesStopped) {
@@ -234,19 +318,50 @@
         }
     };
 
-    private final UEventObserver mObserver = new UEventObserver() {
-        @Override
-        public void onUEvent(UEventObserver.UEvent event) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Slog.v(TAG, "Dock UEVENT: " + event.toString());
+    private int getDockedStateExtraValue(ExtconStateProvider state) {
+        for (ExtconStateConfig config : mExtconStateConfigs) {
+            boolean match = true;
+            for (Pair<String, String> keyValue : config.keyValuePairs) {
+                String stateValue = state.getValue(keyValue.first);
+                match = match && keyValue.second.equals(stateValue);
+                if (!match) {
+                    break;
+                }
             }
 
-            try {
-                synchronized (mLock) {
-                    setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE")));
+            if (match) {
+                return config.extraStateValue;
+            }
+        }
+
+        return Intent.EXTRA_DOCK_STATE_DESK;
+    }
+
+    @VisibleForTesting
+    void setDockStateFromProviderForTesting(ExtconStateProvider provider) {
+        synchronized (mLock) {
+            setDockStateFromProviderLocked(provider);
+        }
+    }
+
+    private void setDockStateFromProviderLocked(ExtconStateProvider provider) {
+        int state = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+        if ("1".equals(provider.getValue("DOCK"))) {
+            state = getDockedStateExtraValue(provider);
+        }
+        setActualDockStateLocked(state);
+    }
+
+    private final ExtconUEventObserver mExtconUEventObserver = new ExtconUEventObserver() {
+        @Override
+        public void onUEvent(ExtconInfo extconInfo, UEventObserver.UEvent event) {
+            synchronized (mLock) {
+                String stateString = event.get("STATE");
+                if (stateString != null) {
+                    setDockStateFromProviderLocked(ExtconStateProvider.fromString(stateString));
+                } else {
+                    Slog.e(TAG, "Extcon event missing STATE: " + event);
                 }
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Could not parse switch state from event " + event);
             }
         }
     };
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 0bc3fcc..ce30f03 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -576,19 +576,20 @@
         return () -> {
             final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace);
             final String serviceName = service.getClass().getName();
+            final int curUserId = curUser.getUserIdentifier();
+            t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName);
             try {
-                final int curUserId = curUser.getUserIdentifier();
-                t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName);
                 long time = SystemClock.elapsedRealtime();
                 service.onUserStarting(curUser);
                 warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
                         "on" + USER_STARTING + "User-" + curUserId);
-                t.traceEnd();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Failure reporting " + USER_STARTING + " of user " + curUser
                         + " to service " + serviceName, e);
                 Slog.e(TAG, "Disabling thread pool - please capture a bug report.");
                 sUseLifecycleThreadPool = false;
+            } finally {
+                t.traceEnd();
             }
         };
     }
@@ -601,11 +602,18 @@
             final int curUserId = curUser.getUserIdentifier();
             t.traceBegin("ssm.on" + USER_COMPLETED_EVENT + "User-" + curUserId
                     + "_" + eventType + "_" + serviceName);
-            long time = SystemClock.elapsedRealtime();
-            service.onUserCompletedEvent(curUser, eventType);
-            warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
-                    "on" + USER_COMPLETED_EVENT + "User-" + curUserId);
-            t.traceEnd();
+            try {
+                long time = SystemClock.elapsedRealtime();
+                service.onUserCompletedEvent(curUser, eventType);
+                warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
+                        "on" + USER_COMPLETED_EVENT + "User-" + curUserId);
+            } catch (Exception e) {
+                Slog.wtf(TAG, "Failure reporting " + USER_COMPLETED_EVENT + " of user " + curUser
+                        + " to service " + serviceName, e);
+                throw e;
+            } finally {
+                t.traceEnd();
+            }
         };
     }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b5c0a67..9353dd8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2721,7 +2721,8 @@
 
     int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, final IServiceConnection connection, int flags,
-            String instanceName, String callingPackage, final int userId)
+            String instanceName, boolean isSupplementalProcessService, String callingPackage,
+            final int userId)
             throws TransactionTooLargeException {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
                 + " type=" + resolvedType + " conn=" + connection.asBinder()
@@ -2805,10 +2806,9 @@
         final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
         final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
 
-        ServiceLookupResult res =
-            retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
-                    callingPid, callingUid, userId, true,
-                    callerFg, isBindExternal, allowInstant);
+        ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
+                isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid,
+                userId, true, callerFg, isBindExternal, allowInstant);
         if (res == null) {
             return 0;
         }
@@ -3228,6 +3228,20 @@
             int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
             boolean allowInstant) {
+        return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage,
+                callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
+                allowInstant);
+    }
+
+    private ServiceLookupResult retrieveServiceLocked(Intent service,
+            String instanceName, boolean isSupplementalProcessService, String resolvedType,
+            String callingPackage, int callingPid, int callingUid, int userId,
+            boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
+            boolean allowInstant) {
+        if (isSupplementalProcessService && instanceName == null) {
+            throw new IllegalArgumentException("No instanceName provided for supplemental process");
+        }
+
         ServiceRecord r = null;
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
                 + " type=" + resolvedType + " callingUid=" + callingUid);
@@ -3249,7 +3263,6 @@
         if (instanceName == null) {
             comp = service.getComponent();
         } else {
-            // This is for isolated services
             final ComponentName realComp = service.getComponent();
             if (realComp == null) {
                 throw new IllegalArgumentException("Can't use custom instance name '" + instanceName
@@ -3304,12 +3317,19 @@
                     return null;
                 }
                 if (instanceName != null
-                        && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+                        && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0
+                        && !isSupplementalProcessService) {
                     throw new IllegalArgumentException("Can't use instance name '" + instanceName
-                            + "' with non-isolated service '" + sInfo.name + "'");
+                            + "' with non-isolated non-supplemental service '" + sInfo.name + "'");
                 }
-                ComponentName className = new ComponentName(
-                        sInfo.applicationInfo.packageName, sInfo.name);
+                if (isSupplementalProcessService
+                        && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+                    throw new IllegalArgumentException("Service cannot be both supplemental and "
+                            + "isolated");
+                }
+
+                ComponentName className = new ComponentName(sInfo.applicationInfo.packageName,
+                                                            sInfo.name);
                 ComponentName name = comp != null ? comp : className;
                 if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid,
                         name.getPackageName(), sInfo.applicationInfo.uid)) {
@@ -3392,7 +3412,8 @@
                             = new Intent.FilterComparison(service.cloneFilter());
                     final ServiceRestarter res = new ServiceRestarter();
                     r = new ServiceRecord(mAm, className, name, definingPackageName,
-                            definingUid, filter, sInfo, callingFromFg, res);
+                            definingUid, filter, sInfo, callingFromFg, res,
+                            isSupplementalProcessService);
                     res.setService(r);
                     smap.mServicesByInstanceName.put(name, r);
                     smap.mServicesByIntent.put(filter, r);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 9a1bfdd..d9ee7d9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -18,6 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.TransactionTooLargeException;
 
 /**
  * Interface for in-process calls into
@@ -58,4 +61,24 @@
      * @hide
      */
     void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
+
+    /**
+     * Starts a supplemental process service and binds to it. You can through the arguments here
+     * have the system bring up multiple concurrent processes hosting their own instance of that
+     * service. The <var>userAppUid</var> you provide here identifies the different instances - each
+     * unique uid is attributed to a supplemental process.
+     *
+     * @param service Identifies the supplemental process service to connect to. The Intent must
+     *        specify an explicit component name. This value cannot be null.
+     * @param conn Receives information as the service is started and stopped.
+     *        This must be a valid ServiceConnection object; it must not be null.
+     * @param userAppUid Uid of the app for which the supplemental process needs to be spawned.
+     * @return {@code true} if the system is in the process of bringing up a
+     *         service that your client has permission to bind to; {@code false}
+     *         if the system couldn't find the service or if your client doesn't
+     *         have permission to bind to it.
+     */
+    boolean startAndBindSupplementalProcessService(@NonNull Intent service,
+            @NonNull ServiceConnection conn, int userAppUid) throws TransactionTooLargeException;
+
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 902659c..fafe908 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -218,6 +218,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.LocusId;
+import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.ApplicationInfo;
@@ -256,6 +257,7 @@
 import android.os.BugreportParams;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.Debug;
 import android.os.DropBoxManager;
 import android.os.FactoryTest;
@@ -336,7 +338,7 @@
 import com.android.internal.app.SystemUserHomeActivity;
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BackgroundThread;
@@ -457,7 +459,7 @@
      * broadcasts
      */
     private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT =
-            SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false);
+            SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", true);
 
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
     static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
@@ -2899,16 +2901,31 @@
         mActivityTaskManager.setPackageScreenCompatMode(packageName, mode);
     }
 
-    private boolean hasUsageStatsPermission(String callingPackage) {
+    private boolean hasUsageStatsPermission(String callingPackage, int callingUid, int callingPid) {
         final int mode = mAppOpsService.noteOperation(AppOpsManager.OP_GET_USAGE_STATS,
-                Binder.getCallingUid(), callingPackage, null, false, "", false).getOpMode();
+                callingUid, callingPackage, null, false, "", false).getOpMode();
         if (mode == AppOpsManager.MODE_DEFAULT) {
-            return checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+            return checkPermission(Manifest.permission.PACKAGE_USAGE_STATS, callingPid, callingUid)
                     == PackageManager.PERMISSION_GRANTED;
         }
         return mode == AppOpsManager.MODE_ALLOWED;
     }
 
+    private boolean hasUsageStatsPermission(String callingPackage) {
+        return hasUsageStatsPermission(callingPackage,
+                Binder.getCallingUid(), Binder.getCallingPid());
+    }
+
+    private void enforceUsageStatsPermission(String callingPackage,
+            int callingUid, int callingPid, String operation) {
+        if (!hasUsageStatsPermission(callingPackage, callingUid, callingPid)) {
+            final String errorMsg = "Permission denial for <" + operation + "> from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " which requires PACKAGE_USAGE_STATS permission";
+            throw new SecurityException(errorMsg);
+        }
+    }
+
     @Override
     public int getPackageProcessState(String packageName, String callingPackage) {
         if (!hasUsageStatsPermission(callingPackage)) {
@@ -4951,7 +4968,7 @@
             // This line is needed to CTS test for the correct exception handling
             // See b/138952436#comment36 for context
             Slog.i(TAG, "About to commit checkpoint");
-            IStorageManager storageManager = PackageHelper.getStorageManager();
+            IStorageManager storageManager = InstallLocationUtils.getStorageManager();
             storageManager.commitChanges();
         } catch (Exception e) {
             PowerManager pm = (PowerManager)
@@ -12314,13 +12331,25 @@
     public int bindService(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, IServiceConnection connection, int flags,
             String callingPackage, int userId) throws TransactionTooLargeException {
-        return bindIsolatedService(caller, token, service, resolvedType, connection, flags,
+        return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
                 null, callingPackage, userId);
     }
 
-    public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service,
+    /**
+     * Binds to a service with a given instanceName, creating it if it does not already exist.
+     * If the instanceName field is not supplied, binding to the service occurs as usual.
+     */
+    public int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, IServiceConnection connection, int flags, String instanceName,
             String callingPackage, int userId) throws TransactionTooLargeException {
+        return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
+                instanceName, false, callingPackage, userId);
+    }
+
+    private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
+            String resolvedType, IServiceConnection connection, int flags, String instanceName,
+            boolean isSupplementalProcessService, String callingPackage, int userId)
+            throws TransactionTooLargeException {
         enforceNotIsolatedCaller("bindService");
 
         // Refuse possible leaked file descriptors
@@ -12332,6 +12361,10 @@
             throw new IllegalArgumentException("callingPackage cannot be null");
         }
 
+        if (isSupplementalProcessService && instanceName == null) {
+            throw new IllegalArgumentException("No instance name provided for isolated process");
+        }
+
         // Ensure that instanceName, which is caller provided, does not contain
         // unusual characters.
         if (instanceName != null) {
@@ -12345,8 +12378,8 @@
         }
 
         synchronized(this) {
-            return mServices.bindServiceLocked(caller, token, service,
-                    resolvedType, connection, flags, instanceName, callingPackage, userId);
+            return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
+                    flags, instanceName, isSupplementalProcessService, callingPackage, userId);
         }
     }
 
@@ -12765,7 +12798,7 @@
                 noAction.add(null);
                 actions = noAction.iterator();
             }
-            boolean onlyProtectedBroadcasts = actions.hasNext();
+            boolean onlyProtectedBroadcasts = true;
 
             // Collect stickies of users and check if broadcast is only registered for protected
             // broadcasts
@@ -12839,6 +12872,8 @@
                     // Change is not enabled, thus not targeting T+. Assume exported.
                     flags |= Context.RECEIVER_EXPORTED;
                 }
+            } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
+                flags |= Context.RECEIVER_EXPORTED;
             }
         }
 
@@ -13331,6 +13366,13 @@
                     backgroundActivityStartsToken = null;
                 }
             }
+
+            // TODO (206518114): We need to use the "real" package name which sent the broadcast,
+            // in case the broadcast is sent via PendingIntent.
+            if (brOptions.getIdForResponseEvent() > 0) {
+                enforceUsageStatsPermission(callerPackage, realCallingUid, realCallingPid,
+                        "recordResponseEventWhileInBackground()");
+            }
         }
 
         // Verify that protected broadcasts are only being sent by system code,
@@ -15478,6 +15520,62 @@
         }
     }
 
+    /**
+     * Dump the resources structure for the given process
+     *
+     * @param process The process to dump resource info for
+     * @param fd The FileDescriptor to dump it into
+     * @throws RemoteException
+     */
+    public boolean dumpResources(String process, ParcelFileDescriptor fd, RemoteCallback callback)
+            throws RemoteException {
+        synchronized (this) {
+            ProcessRecord proc = findProcessLOSP(process, UserHandle.USER_CURRENT, "dumpResources");
+            IApplicationThread thread;
+            if (proc == null || (thread = proc.getThread()) == null) {
+                throw new IllegalArgumentException("Unknown process: " + process);
+            }
+            thread.dumpResources(fd, callback);
+            return true;
+        }
+    }
+
+    /**
+     * Dump the resources structure for all processes
+     *
+     * @param fd The FileDescriptor to dump it into
+     * @throws RemoteException
+     */
+    public void dumpAllResources(ParcelFileDescriptor fd, PrintWriter pw) throws RemoteException {
+        synchronized (mProcLock) {
+            mProcessList.forEachLruProcessesLOSP(true, app -> {
+                ConditionVariable lock = new ConditionVariable();
+                RemoteCallback
+                        finishCallback = new RemoteCallback(result -> lock.open(), null);
+
+                pw.println(String.format("------ DUMP RESOURCES %s (%s)  ------",
+                        app.processName,
+                        app.info.packageName));
+                pw.flush();
+                try {
+                    app.getThread().dumpResources(fd.dup(), finishCallback);
+                    lock.block(2000);
+                } catch (Exception e) {
+                    pw.println(String.format(
+                            "------ EXCEPTION DUMPING RESOURCES for %s (%s): %s ------",
+                            app.processName,
+                            app.info.packageName,
+                            e.getMessage()));
+                    pw.flush();
+                }
+                pw.println(String.format("------ END DUMP RESOURCES %s (%s)  ------",
+                        app.processName,
+                        app.info.packageName));
+                pw.flush();
+            });
+        }
+    }
+
     @Override
     public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize,
             String reportPackage) {
@@ -15823,6 +15921,34 @@
         }
 
         @Override
+        public boolean startAndBindSupplementalProcessService(Intent service,
+                ServiceConnection conn, int userAppUid) throws TransactionTooLargeException {
+            if (service == null) {
+                throw new IllegalArgumentException("intent is null");
+            }
+            if (conn == null) {
+                throw new IllegalArgumentException("connection is null");
+            }
+            if (service.getComponent() == null) {
+                throw new IllegalArgumentException("service must specify explicit component");
+            }
+            if (!UserHandle.isApp(userAppUid)) {
+                throw new IllegalArgumentException("uid is not within application range");
+            }
+
+            Handler handler = mContext.getMainThreadHandler();
+            int flags = Context.BIND_AUTO_CREATE;
+
+            final IServiceConnection sd = mContext.getServiceDispatcher(conn, handler, flags);
+            service.prepareToLeaveProcess(mContext);
+            return ActivityManagerService.this.bindServiceInstance(
+                        mContext.getIApplicationThread(), mContext.getActivityToken(), service,
+                        service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags,
+                        Integer.toString(userAppUid), /*isSupplementalProcessService*/ true,
+                        mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0;
+        }
+
+        @Override
         public void onUserRemoved(@UserIdInt int userId) {
             // Clean up any ActivityTaskManager state (by telling it the user is stopped)
             mAtmInternal.onUserStopped(userId);
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 1315293..465623f 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -54,6 +54,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
 import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+import static android.os.PowerExemptionManager.REASON_CARRIER_PRIVILEGED_APP;
 import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
 import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
@@ -65,6 +66,7 @@
 import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER;
 import static android.os.PowerExemptionManager.REASON_ROLE_DIALER;
 import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
 import static android.os.Process.SYSTEM_UID;
@@ -125,6 +127,7 @@
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -138,6 +141,7 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.server.AppStateTracker;
 import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
 import com.android.server.apphibernation.AppHibernationManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.usage.AppStandbyInternal;
@@ -229,6 +233,24 @@
     @GuardedBy("mLock")
     private final HashMap<String, Boolean> mSystemModulesCache = new HashMap<>();
 
+    /**
+     * The pre-config packages that are exempted from the background restrictions.
+     */
+    private ArraySet<String> mBgRestrictionExemptioFromSysConfig;
+
+    /**
+     * Lock specifically for bookkeeping around the carrier-privileged app set.
+     * Do not acquire any other locks while holding this one. Methods that
+     * require this lock to be held are named with a "CPL" suffix.
+     */
+    private final Object mCarrierPrivilegedLock = new Object();
+
+    /**
+     * List of carrier-privileged apps that should be excluded from standby.
+     */
+    @GuardedBy("mCarrierPrivilegedLock")
+    private List<String> mCarrierPrivilegedApps;
+
     final ActivityManagerService mActivityManagerService;
 
     /**
@@ -690,6 +712,7 @@
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 ActivityThread.currentApplication().getMainExecutor(), mConstantsObserver);
         mConstantsObserver.start();
+        initBgRestrictionExemptioFromSysConfig();
         initRestrictionStates();
         initSystemModuleNames();
         registerForUidObservers();
@@ -711,6 +734,22 @@
         initRestrictionStates();
     }
 
+    private void initBgRestrictionExemptioFromSysConfig() {
+        mBgRestrictionExemptioFromSysConfig =
+                SystemConfig.getInstance().getBgRestrictionExemption();
+        if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+            final ArraySet<String> exemptedPkgs = mBgRestrictionExemptioFromSysConfig;
+            for (int i = exemptedPkgs.size() - 1; i >= 0; i--) {
+                Slog.i(TAG, "bg-restriction-exemption: " + exemptedPkgs.valueAt(i));
+            }
+        }
+    }
+
+    private boolean isExemptedFromSysConfig(String packageName) {
+        return mBgRestrictionExemptioFromSysConfig != null
+                && mBgRestrictionExemptioFromSysConfig.contains(packageName);
+    }
+
     private void initRestrictionStates() {
         final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
         for (int userId : allUsers) {
@@ -1542,14 +1581,11 @@
         }
     }
 
-    boolean isOnDeviceIdleAllowlist(int uid, boolean allowExceptIdle) {
+    boolean isOnDeviceIdleAllowlist(int uid) {
         final int appId = UserHandle.getAppId(uid);
 
-        final int[] allowlist = allowExceptIdle
-                ? mDeviceIdleExceptIdleAllowlist
-                : mDeviceIdleAllowlist;
-
-        return Arrays.binarySearch(allowlist, appId) >= 0;
+        return Arrays.binarySearch(mDeviceIdleAllowlist, appId) >= 0
+                || Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, appId) >= 0;
     }
 
     void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) {
@@ -1570,7 +1606,7 @@
         if (UserHandle.isCore(uid)) {
             return REASON_SYSTEM_UID;
         }
-        if (isOnDeviceIdleAllowlist(uid, false)) {
+        if (isOnDeviceIdleAllowlist(uid)) {
             return REASON_ALLOWLISTED_PACKAGE;
         }
         final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
@@ -1604,6 +1640,10 @@
                     return REASON_OP_ACTIVATE_PLATFORM_VPN;
                 } else if (isSystemModule(pkg)) {
                     return REASON_SYSTEM_MODULE;
+                } else if (isCarrierApp(pkg)) {
+                    return REASON_CARRIER_PRIVILEGED_APP;
+                } else if (isExemptedFromSysConfig(pkg)) {
+                    return REASON_SYSTEM_ALLOW_LISTED;
                 }
             }
         }
@@ -1616,6 +1656,37 @@
         return REASON_DENIED;
     }
 
+    private boolean isCarrierApp(String packageName) {
+        synchronized (mCarrierPrivilegedLock) {
+            if (mCarrierPrivilegedApps == null) {
+                fetchCarrierPrivilegedAppsCPL();
+            }
+            if (mCarrierPrivilegedApps != null) {
+                return mCarrierPrivilegedApps.contains(packageName);
+            }
+            return false;
+        }
+    }
+
+    private void clearCarrierPrivilegedApps() {
+        if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+            Slog.i(TAG, "Clearing carrier privileged apps list");
+        }
+        synchronized (mCarrierPrivilegedLock) {
+            mCarrierPrivilegedApps = null; // Need to be refetched.
+        }
+    }
+
+    @GuardedBy("mCarrierPrivilegedLock")
+    private void fetchCarrierPrivilegedAppsCPL() {
+        final TelephonyManager telephonyManager = mInjector.getTelephonyManager();
+        mCarrierPrivilegedApps =
+                telephonyManager.getCarrierPrivilegedPackagesForAllActiveSubscriptions();
+        if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+            Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
+        }
+    }
+
     private boolean isRoleHeldByUid(@NonNull String roleName, int uid) {
         synchronized (mLock) {
             final ArrayList<String> roles = mUidRolesMapping.get(uid);
@@ -1791,6 +1862,7 @@
         private AppBatteryExemptionTracker mAppBatteryExemptionTracker;
         private AppFGSTracker mAppFGSTracker;
         private AppMediaSessionTracker mAppMediaSessionTracker;
+        private TelephonyManager mTelephonyManager;
 
         Injector(Context context) {
             mContext = context;
@@ -1890,6 +1962,13 @@
             return mRoleManager;
         }
 
+        TelephonyManager getTelephonyManager() {
+            if (mTelephonyManager == null) {
+                mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
+            }
+            return mTelephonyManager;
+        }
+
         AppFGSTracker getAppFGSTracker() {
             return mAppFGSTracker;
         }
@@ -1939,6 +2018,19 @@
                                 onUidAdded(uid);
                             }
                         }
+                    }
+                    // fall through.
+                    case Intent.ACTION_PACKAGE_CHANGED: {
+                        final String pkgName = intent.getData().getSchemeSpecificPart();
+                        final String[] cmpList = intent.getStringArrayExtra(
+                                Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+                        // If this is PACKAGE_ADDED (cmpList == null), or if it's a whole-package
+                        // enable/disable event (cmpList is just the package name itself), drop
+                        // our carrier privileged app & system-app caches and let them refresh
+                        if (cmpList == null
+                                || (cmpList.length == 1 && pkgName.equals(cmpList[0]))) {
+                            clearCarrierPrivilegedApps();
+                        }
                     } break;
                     case Intent.ACTION_PACKAGE_FULLY_REMOVED: {
                         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
@@ -1986,6 +2078,7 @@
         };
         final IntentFilter packageFilter = new IntentFilter();
         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
         packageFilter.addDataScheme("package");
         mContext.registerReceiverForAllUsers(broadcastReceiver, packageFilter, null, mBgHandler);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c8ad0e8..5da461d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1365,7 +1365,7 @@
     }
 
     public void notePhoneDataConnectionState(final int dataType, final boolean hasData,
-            final int serviceType) {
+            final int serviceType, final int nrFrequency) {
         enforceCallingPermission();
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1373,7 +1373,7 @@
             mHandler.post(() -> {
                 synchronized (mStats) {
                     mStats.notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
-                            elapsedRealtime, uptime);
+                            nrFrequency, elapsedRealtime, uptime);
                 }
             });
         }
@@ -1962,6 +1962,32 @@
         }
     }
 
+    /**
+     * Bluetooth on stat logging
+     */
+    public void noteBluetoothOn(int uid, int reason, String packageName) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT,
+                    Binder.getCallingPid(), uid, null);
+        }
+        FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+                uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED,
+                reason, packageName);
+    }
+
+    /**
+     * Bluetooth off stat logging
+     */
+    public void noteBluetoothOff(int uid, int reason, String packageName) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT,
+                    Binder.getCallingPid(), uid, null);
+        }
+        FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+                uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED,
+                reason, packageName);
+    }
+
     @Override
     public void noteBleScanStarted(final WorkSource ws, final boolean isUnoptimized) {
         enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 0c383eb..8ad6260 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -334,7 +334,7 @@
         mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
 
         // Tell the application to launch this receiver.
-        maybeReportBroadcastDispatchedEventLocked(r);
+        maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
         r.intent.setComponent(r.curComponent);
 
         boolean started = false;
@@ -870,7 +870,7 @@
                     + " due to receiver " + filter.receiverList.app
                     + " (uid " + filter.receiverList.uid + ")"
                     + " not specifying RECEIVER_EXPORTED");
-            // skip = true;
+            skip = true;
         }
 
         if (skip) {
@@ -927,7 +927,7 @@
                 r.receiverTime = SystemClock.uptimeMillis();
                 maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
                 maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
-                maybeReportBroadcastDispatchedEventLocked(r);
+                maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
                 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                         new Intent(r.intent), r.resultCode, r.resultData,
                         r.resultExtras, r.ordered, r.initialSticky, r.userId);
@@ -1856,7 +1856,7 @@
         return null;
     }
 
-    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r) {
+    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
         final String targetPackage = getTargetPackage(r);
         // Ignore non-explicit broadcasts
         if (targetPackage == null) {
@@ -1867,11 +1867,10 @@
         if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
             return;
         }
-        // TODO (206518114): Only report this event when the broadcast is dispatched while the app
-        // is in the background state.
         getUsageStatsManagerInternal().reportBroadcastDispatched(
                 r.callingUid, targetPackage, UserHandle.of(r.userId),
-                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime());
+                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
+                mService.getUidStateLocked(targetUid));
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/am/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java
index 6e39a4c..f0910dc 100644
--- a/services/core/java/com/android/server/am/DataConnectionStats.java
+++ b/services/core/java/com/android/server/am/DataConnectionStats.java
@@ -109,7 +109,7 @@
         }
         try {
             mBatteryStats.notePhoneDataConnectionState(networkType, visible,
-                    mServiceState.getState());
+                    mServiceState.getState(), mServiceState.getNrFrequencyRange());
         } catch (RemoteException e) {
             Log.w(TAG, "Error noting data connection state", e);
         }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 24e7ba4..da78e2d 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -570,6 +570,14 @@
             ComponentName instanceName, String definingPackageName, int definingUid,
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
             Runnable restarter) {
+        this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg,
+                restarter, false);
+    }
+
+    ServiceRecord(ActivityManagerService ams, ComponentName name,
+            ComponentName instanceName, String definingPackageName, int definingUid,
+            Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
+            Runnable restarter, boolean isSupplementalProcessService) {
         this.ams = ams;
         this.name = name;
         this.instanceName = instanceName;
@@ -580,7 +588,8 @@
         serviceInfo = sInfo;
         appInfo = sInfo.applicationInfo;
         packageName = sInfo.applicationInfo.packageName;
-        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+                || isSupplementalProcessService) {
             processName = sInfo.processName + ":" + instanceName.getClassName();
         } else {
             processName = sInfo.processName;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 3c9d29d..551773e 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -20,6 +20,11 @@
 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 
+import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
+import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
+import static com.android.internal.R.styleable.GameModeConfig_allowGameFpsOverride;
+import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode;
+import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode;
 import static com.android.server.wm.CompatModePackages.DOWNSCALED;
 import static com.android.server.wm.CompatModePackages.DOWNSCALE_30;
 import static com.android.server.wm.CompatModePackages.DOWNSCALE_35;
@@ -54,6 +59,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.hardware.power.Mode;
 import android.net.Uri;
 import android.os.Binder;
@@ -71,8 +80,10 @@
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 import android.util.ArrayMap;
+import android.util.AttributeSet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
+import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -84,7 +95,11 @@
 import com.android.server.SystemService;
 import com.android.server.SystemService.TargetUser;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.List;
 
@@ -425,12 +440,20 @@
         public static final String METADATA_BATTERY_MODE_ENABLE =
                 "com.android.app.gamemode.battery.enabled";
 
+        /**
+         * Metadata that allows a game to specify all intervention information with an XML file in
+         * the application field.
+         */
+        public static final String METADATA_GAME_MODE_CONFIG = "android.game_mode_config";
+
+        private static final String GAME_MODE_CONFIG_NODE_NAME = "game-mode-config";
         private final String mPackageName;
         private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
         private boolean mPerfModeOptedIn;
         private boolean mBatteryModeOptedIn;
         private boolean mAllowDownscale;
         private boolean mAllowAngle;
+        private boolean mAllowFpsOverride;
 
         GamePackageConfiguration(String packageName, int userId) {
             mPackageName = packageName;
@@ -438,18 +461,21 @@
             try {
                 final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName,
                         PackageManager.GET_META_DATA, userId);
-                if (ai.metaData != null) {
-                    mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
-                    mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
-                    mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
-                    mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
-                } else {
-                    mPerfModeOptedIn = false;
-                    mBatteryModeOptedIn = false;
-                    mAllowDownscale = true;
-                    mAllowAngle = true;
+                if (!parseInterventionFromXml(ai, packageName)) {
+                    if (ai.metaData != null) {
+                        mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
+                        mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
+                        mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
+                        mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
+                    } else {
+                        mPerfModeOptedIn = false;
+                        mBatteryModeOptedIn = false;
+                        mAllowDownscale = true;
+                        mAllowAngle = true;
+                        mAllowFpsOverride = true;
+                    }
                 }
-            } catch (PackageManager.NameNotFoundException e) {
+            } catch (NameNotFoundException e) {
                 // Not all packages are installed, hence ignore those that are not installed yet.
                 Slog.v(TAG, "Failed to get package metadata");
             }
@@ -469,6 +495,53 @@
             }
         }
 
+        private boolean parseInterventionFromXml(ApplicationInfo ai, String packageName) {
+            boolean xmlFound = false;
+            try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager,
+                    METADATA_GAME_MODE_CONFIG)) {
+                if (parser == null) {
+                    Slog.v(TAG, "No " + METADATA_GAME_MODE_CONFIG
+                            + " meta-data found for package " + mPackageName);
+                } else {
+                    xmlFound = true;
+                    final Resources resources = mPackageManager.getResourcesForApplication(
+                            packageName);
+                    final AttributeSet attributeSet = Xml.asAttributeSet(parser);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && type != XmlPullParser.START_TAG) {
+                        // Do nothing
+                    }
+
+                    boolean isStartingTagGameModeConfig =
+                            GAME_MODE_CONFIG_NODE_NAME.equals(parser.getName());
+                    if (!isStartingTagGameModeConfig) {
+                        Slog.w(TAG, "Meta-data does not start with "
+                                + GAME_MODE_CONFIG_NODE_NAME
+                                + " tag");
+                    } else {
+                        final TypedArray array = resources.obtainAttributes(attributeSet,
+                                com.android.internal.R.styleable.GameModeConfig);
+                        mPerfModeOptedIn = array.getBoolean(
+                                GameModeConfig_supportsPerformanceGameMode, false);
+                        mBatteryModeOptedIn = array.getBoolean(
+                                GameModeConfig_supportsBatteryGameMode,
+                                false);
+                        mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
+                                true);
+                        mAllowAngle = array.getBoolean(GameModeConfig_allowGameAngleDriver, true);
+                        mAllowFpsOverride = array.getBoolean(GameModeConfig_allowGameFpsOverride,
+                                true);
+                        array.recycle();
+                    }
+                }
+            } catch (NameNotFoundException | XmlPullParserException | IOException ex) {
+                Slog.e(TAG, "Error while parsing XML meta-data for "
+                        + METADATA_GAME_MODE_CONFIG);
+            }
+            return xmlFound;
+        }
+
         /**
          * GameModeConfiguration contains all the values for all the interventions associated with
          * a game mode.
@@ -497,7 +570,8 @@
                 mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
                         ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
 
-                mFps = parser.getString(FPS_KEY, DEFAULT_FPS);
+                mFps = mAllowFpsOverride && !willGamePerformOptimizations(mGameMode)
+                        ? parser.getString(FPS_KEY, DEFAULT_FPS) : DEFAULT_FPS;
                 // We only want to use ANGLE if:
                 // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
                 // - The app has not opted in to performing the work itself AND
@@ -691,7 +765,7 @@
         try {
             return mPackageManager.getPackageUidAsUser(packageName, userId)
                     == Binder.getCallingUid();
-        } catch (PackageManager.NameNotFoundException e) {
+        } catch (NameNotFoundException e) {
             return false;
         }
     }
@@ -855,7 +929,7 @@
      */
     @Override
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
-    public @GameMode boolean getAngleEnabled(String packageName, int userId)
+    public @GameMode boolean isAngleEnabled(String packageName, int userId)
             throws SecurityException {
         final int gameMode = getGameMode(packageName, userId);
         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
@@ -1413,7 +1487,7 @@
                         if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
                             return;
                         }
-                    } catch (PackageManager.NameNotFoundException e) {
+                    } catch (NameNotFoundException e) {
                         // Ignore the exception.
                     }
                     switch (intent.getAction()) {
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 9136219..960fbf1 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -50,6 +50,7 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.ServiceConnector;
 import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
 import com.android.server.wm.WindowManagerService;
 
 import java.util.List;
@@ -62,6 +63,18 @@
     private static final int CREATE_GAME_SESSION_TIMEOUT_MS = 10_000;
     private static final boolean DEBUG = false;
 
+    private final TaskSystemBarsListener mTaskSystemBarsVisibilityListener =
+            new TaskSystemBarsListener() {
+                @Override
+                public void onTransientSystemBarsVisibilityChanged(
+                        int taskId,
+                        boolean visible,
+                        boolean wereRevealedFromSwipeOnSystemBar) {
+                    GameServiceProviderInstanceImpl.this.onTransientSystemBarsVisibilityChanged(
+                            taskId, visible, wereRevealedFromSwipeOnSystemBar);
+                }
+            };
+
     private final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
         public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
@@ -98,7 +111,10 @@
     private final IGameServiceController mGameServiceController =
             new IGameServiceController.Stub() {
                 @Override
+                @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
                 public void createGameSession(int taskId) {
+                    mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY,
+                            "createGameSession()");
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.createGameSession(taskId);
                     });
@@ -116,9 +132,10 @@
                     });
                 }
 
-                @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                @Override
+                @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
                 public void restartGame(int taskId) {
-                    mContext.enforceCallingPermission(Manifest.permission.FORCE_STOP_PACKAGES,
+                    mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY,
                             "restartGame()");
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.restartGame(taskId);
@@ -199,6 +216,8 @@
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to register task stack listener", e);
         }
+
+        mWindowManagerInternal.registerTaskSystemBarsListener(mTaskSystemBarsVisibilityListener);
     }
 
     @GuardedBy("mLock")
@@ -214,6 +233,9 @@
             Slog.w(TAG, "Failed to unregister task stack listener", e);
         }
 
+        mWindowManagerInternal.unregisterTaskSystemBarsListener(
+                mTaskSystemBarsVisibilityListener);
+
         for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
             destroyGameSessionFromRecord(gameSessionRecord);
         }
@@ -303,6 +325,37 @@
         }
     }
 
+    private void onTransientSystemBarsVisibilityChanged(
+            int taskId,
+            boolean visible,
+            boolean wereRevealedFromSwipeOnSystemBar) {
+        if (visible && !wereRevealedFromSwipeOnSystemBar) {
+            return;
+        }
+
+        GameSessionRecord gameSessionRecord;
+        synchronized (mLock) {
+            gameSessionRecord = mGameSessions.get(taskId);
+        }
+
+        if (gameSessionRecord == null) {
+            return;
+        }
+
+        IGameSession gameSession = gameSessionRecord.getGameSession();
+        if (gameSession == null) {
+            return;
+        }
+
+        try {
+            gameSession.onTransientSystemBarVisibilityFromRevealGestureChanged(visible);
+        } catch (RemoteException ex) {
+            Slog.w(TAG,
+                    "Failed to send transient system bars visibility from reveal gesture for task: "
+                            + taskId);
+        }
+    }
+
     private void createGameSession(int taskId) {
         synchronized (mLock) {
             createGameSessionLocked(taskId);
@@ -372,12 +425,12 @@
                         }, mBackgroundExecutor);
 
         AndroidFuture<Void> unusedPostCreateGameSessionFuture =
-                mGameSessionServiceConnector.post(gameService -> {
+                mGameSessionServiceConnector.post(gameSessionService -> {
                     CreateGameSessionRequest createGameSessionRequest =
                             new CreateGameSessionRequest(
                                     taskId,
                                     existingGameSessionRecord.getComponentName().getPackageName());
-                    gameService.create(
+                    gameSessionService.create(
                             mGameSessionController,
                             createGameSessionRequest,
                             gameSessionViewHostConfiguration,
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index a139589..d2fa386 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -22,6 +22,7 @@
 import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
 import static android.service.attention.AttentionService.ATTENTION_FAILURE_CANCELLED;
 import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNOWN;
+import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -29,6 +30,7 @@
 import android.app.ActivityThread;
 import android.attention.AttentionManagerInternal;
 import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.attention.AttentionManagerInternal.ProximityCallbackInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -57,6 +59,7 @@
 import android.service.attention.AttentionService.AttentionSuccessCodes;
 import android.service.attention.IAttentionCallback;
 import android.service.attention.IAttentionService;
+import android.service.attention.IProximityCallback;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -134,6 +137,15 @@
     @GuardedBy("mLock")
     AttentionCheck mCurrentAttentionCheck;
 
+    /**
+     * A proxy for relaying proximity information between the Attention Service and the client.
+     * The proxy will be initialized when the client calls onStartProximityUpdates and will be
+     * disabled only when the client calls onStopProximityUpdates.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    ProximityUpdate mCurrentProximityUpdate;
+
     public AttentionManagerService(Context context) {
         this(context, (PowerManager) context.getSystemService(Context.POWER_SERVICE),
                 new Object(), null);
@@ -315,6 +327,77 @@
         }
     }
 
+    /**
+     * Requests the continuous updates of proximity signal via the provided callback,
+     * until the given callback is stopped.
+     *
+     * Calling this multiple times for duplicate requests will be no-ops, returning true.
+     *
+     * @return {@code true} if the framework was able to dispatch the request
+     */
+    @VisibleForTesting
+    boolean onStartProximityUpdates(ProximityCallbackInternal callbackInternal) {
+        Objects.requireNonNull(callbackInternal);
+        if (!mIsServiceEnabled) {
+            Slog.w(LOG_TAG, "Trying to call onProximityUpdate() on an unsupported device.");
+            return false;
+        }
+
+        if (!isServiceAvailable()) {
+            Slog.w(LOG_TAG, "Service is not available at this moment.");
+            return false;
+        }
+
+        // don't allow proximity request in screen off state.
+        // This behavior might change in the future.
+        if (!mPowerManager.isInteractive()) {
+            Slog.w(LOG_TAG, "Proximity Service is unavailable during screen off at this moment.");
+            return false;
+        }
+
+        synchronized (mLock) {
+            // schedule shutting down the connection if no one resets this timer
+            freeIfInactiveLocked();
+
+            // lazily start the service, which should be very lightweight to start
+            bindLocked();
+
+            /*
+            Prevent spamming with multiple requests, only one at a time is allowed.
+            If there are use-cases for keeping track of multiple requests, we
+            can refactor ProximityUpdate object to keep track of multiple internal callbacks.
+             */
+            if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) {
+                if (mCurrentProximityUpdate.mCallbackInternal == callbackInternal) {
+                    Slog.w(LOG_TAG, "Provided callback is already registered. Skipping.");
+                    return true;
+                } else {
+                    // reject the new request since the old request is still alive.
+                    Slog.w(LOG_TAG, "New proximity update cannot be processed because there is "
+                            + "already an ongoing update");
+                    return false;
+                }
+            }
+            mCurrentProximityUpdate = new ProximityUpdate(callbackInternal);
+            return mCurrentProximityUpdate.startUpdates();
+        }
+    }
+
+    /** Cancels the specified proximity registration. */
+    @VisibleForTesting
+    void onStopProximityUpdates(ProximityCallbackInternal callbackInternal) {
+        synchronized (mLock) {
+            if (mCurrentProximityUpdate == null
+                    || !mCurrentProximityUpdate.mCallbackInternal.equals(callbackInternal)
+                    || !mCurrentProximityUpdate.mStartedUpdates) {
+                Slog.w(LOG_TAG, "Cannot stop a non-current callback");
+                return;
+            }
+            mCurrentProximityUpdate.cancelUpdates();
+            mCurrentProximityUpdate = null;
+        }
+    }
+
     @GuardedBy("mLock")
     @VisibleForTesting
     protected void freeIfInactiveLocked() {
@@ -390,15 +473,18 @@
             ipw.println("Class=" + mComponentName.getClassName());
             ipw.decreaseIndent();
         }
-        ipw.println("binding=" + mBinding);
-        ipw.println("current attention check:");
         synchronized (mLock) {
+            ipw.println("binding=" + mBinding);
+            ipw.println("current attention check:");
             if (mCurrentAttentionCheck != null) {
                 mCurrentAttentionCheck.dump(ipw);
             }
             if (mAttentionCheckCacheBuffer != null) {
                 mAttentionCheckCacheBuffer.dump(ipw);
             }
+            if (mCurrentProximityUpdate != null) {
+                mCurrentProximityUpdate.dump(ipw);
+            }
         }
     }
 
@@ -417,6 +503,17 @@
         public void cancelAttentionCheck(AttentionCallbackInternal callbackInternal) {
             AttentionManagerService.this.cancelAttentionCheck(callbackInternal);
         }
+
+        @Override
+        public boolean onStartProximityUpdates(
+                ProximityCallbackInternal callback) {
+            return AttentionManagerService.this.onStartProximityUpdates(callback);
+        }
+
+        @Override
+        public void onStopProximityUpdates(ProximityCallbackInternal callback) {
+            AttentionManagerService.this.onStopProximityUpdates(callback);
+        }
     }
 
     @VisibleForTesting
@@ -536,6 +633,71 @@
         }
     }
 
+    @VisibleForTesting
+    final class ProximityUpdate {
+        private final ProximityCallbackInternal mCallbackInternal;
+        private final IProximityCallback mIProximityCallback;
+        private boolean mStartedUpdates;
+
+        ProximityUpdate(ProximityCallbackInternal callbackInternal) {
+            mCallbackInternal = callbackInternal;
+            mIProximityCallback = new IProximityCallback.Stub() {
+                @Override
+                public void onProximityUpdate(double distance) {
+                    synchronized (mLock) {
+                        mCallbackInternal.onProximityUpdate(distance);
+                        freeIfInactiveLocked();
+                    }
+                }
+            };
+        }
+
+        boolean startUpdates() {
+            synchronized (mLock) {
+                if (mStartedUpdates) {
+                    Slog.w(LOG_TAG, "Already registered to a proximity service.");
+                    return false;
+                }
+                if (mService == null) {
+                    Slog.w(LOG_TAG,
+                            "There is no service bound. Proximity update request rejected.");
+                    return false;
+                }
+                try {
+                    mService.onStartProximityUpdates(mIProximityCallback);
+                    mStartedUpdates = true;
+                } catch (RemoteException e) {
+                    Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        void cancelUpdates() {
+            synchronized (mLock) {
+                if (mStartedUpdates) {
+                    if (mService == null) {
+                        mStartedUpdates = false;
+                        return;
+                    }
+                    try {
+                        mService.onStopProximityUpdates();
+                        mStartedUpdates = false;
+                    } catch (RemoteException e) {
+                        Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+                    }
+                }
+            }
+        }
+
+        void dump(IndentingPrintWriter ipw) {
+            ipw.increaseIndent();
+            ipw.println("is StartedUpdates=" + mStartedUpdates);
+            ipw.decreaseIndent();
+        }
+    }
+
     private void appendResultToAttentionCacheBuffer(AttentionCheckCache cache) {
         synchronized (mLock) {
             if (mAttentionCheckCacheBuffer == null) {
@@ -593,6 +755,18 @@
                 mCurrentAttentionCheck.mCallbackInternal.onFailure(ATTENTION_FAILURE_UNKNOWN);
             }
         }
+        if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) {
+            if (mService != null) {
+                try {
+                    mService.onStartProximityUpdates(mCurrentProximityUpdate.mIProximityCallback);
+                } catch (RemoteException e) {
+                    Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+                }
+            } else {
+                mCurrentProximityUpdate.cancelUpdates();
+                mCurrentProximityUpdate = null;
+            }
+        }
     }
 
     @VisibleForTesting
@@ -609,7 +783,9 @@
             switch (msg.what) {
                 // Do not occupy resources when not in use - unbind proactively.
                 case CHECK_CONNECTION_EXPIRATION: {
-                    cancelAndUnbindLocked();
+                    synchronized (mLock) {
+                        cancelAndUnbindLocked();
+                    }
                 }
                 break;
 
@@ -653,10 +829,15 @@
     @GuardedBy("mLock")
     private void cancelAndUnbindLocked() {
         synchronized (mLock) {
-            if (mCurrentAttentionCheck == null) {
+            if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) {
                 return;
             }
-            cancel();
+            if (mCurrentAttentionCheck != null) {
+                cancel();
+            }
+            if (mCurrentProximityUpdate != null) {
+                mCurrentProximityUpdate.cancelUpdates();
+            }
             if (mService == null) {
                 return;
             }
@@ -702,7 +883,9 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
-                cancelAndUnbindLocked();
+                synchronized (mLock) {
+                    cancelAndUnbindLocked();
+                }
             }
         }
     }
@@ -730,8 +913,27 @@
             }
         }
 
+        class TestableProximityCallbackInternal extends ProximityCallbackInternal {
+            private double mLastCallbackCode = PROXIMITY_UNKNOWN;
+
+            @Override
+            public void onProximityUpdate(double distance) {
+                mLastCallbackCode = distance;
+            }
+
+            public void reset() {
+                mLastCallbackCode = PROXIMITY_UNKNOWN;
+            }
+
+            public double getLastCallbackCode() {
+                return mLastCallbackCode;
+            }
+        }
+
         final TestableAttentionCallbackInternal mTestableAttentionCallback =
                 new TestableAttentionCallbackInternal();
+        final TestableProximityCallbackInternal mTestableProximityCallback =
+                new TestableProximityCallbackInternal();
 
         @Override
         public int onCommand(@Nullable final String cmd) {
@@ -749,6 +951,10 @@
                                 return cmdCallCheckAttention();
                             case "cancelCheckAttention":
                                 return cmdCallCancelAttention();
+                            case "onStartProximityUpdates":
+                                return cmdCallOnStartProximityUpdates();
+                            case "onStopProximityUpdates":
+                                return cmdCallOnStopProximityUpdates();
                             default:
                                 throw new IllegalArgumentException("Invalid argument");
                         }
@@ -758,6 +964,8 @@
                         return cmdClearTestableAttentionService();
                     case "getLastTestCallbackCode":
                         return cmdGetLastTestCallbackCode();
+                    case "getLastTestProximityCallbackCode":
+                        return cmdGetLastTestProximityCallbackCode();
                     default:
                         return handleDefaultCommands(cmd);
                 }
@@ -782,6 +990,7 @@
         private int cmdClearTestableAttentionService() {
             sTestAttentionServicePackage = "";
             mTestableAttentionCallback.reset();
+            mTestableProximityCallback.reset();
             resetStates();
             return 0;
         }
@@ -800,6 +1009,20 @@
             return 0;
         }
 
+        private int cmdCallOnStartProximityUpdates() {
+            final PrintWriter out = getOutPrintWriter();
+            boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityCallback);
+            out.println(calledSuccessfully ? "true" : "false");
+            return 0;
+        }
+
+        private int cmdCallOnStopProximityUpdates() {
+            final PrintWriter out = getOutPrintWriter();
+            onStopProximityUpdates(mTestableProximityCallback);
+            out.println("true");
+            return 0;
+        }
+
         private int cmdResolveAttentionServiceComponent() {
             final PrintWriter out = getOutPrintWriter();
             ComponentName resolvedComponent = resolveAttentionService(mContext);
@@ -813,7 +1036,16 @@
             return 0;
         }
 
+        private int cmdGetLastTestProximityCallbackCode() {
+            final PrintWriter out = getOutPrintWriter();
+            out.println(mTestableProximityCallback.getLastCallbackCode());
+            return 0;
+        }
+
         private void resetStates() {
+            synchronized (mLock) {
+                mCurrentProximityUpdate = null;
+            }
             mComponentName = resolveAttentionService(mContext);
         }
 
@@ -844,11 +1076,24 @@
                             + " (to see the result, call getLastTestCallbackCode)");
             out.println("       := false, otherwise");
             out.println("  call cancelCheckAttention: Cancels check attention");
+            out.println("  call onStartProximityUpdates: Calls onStartProximityUpdates");
+            out.println("  ---returns:");
+            out.println(
+                    "       := true, if the request was successfully dispatched to the service "
+                            + "implementation."
+                            + " (to see the result, call getLastTestProximityCallbackCode)");
+            out.println("       := false, otherwise");
+            out.println("  call onStopProximityUpdates: Cancels proximity updates");
             out.println("  getLastTestCallbackCode");
             out.println("  ---returns:");
             out.println(
                     "       := An integer, representing the last callback code received from the "
                             + "bounded implementation. If none, it will return -1");
+            out.println("  getLastTestProximityCallbackCode");
+            out.println("  ---returns:");
+            out.println(
+                    "       := A double, representing the last proximity value received from the "
+                            + "bounded implementation. If none, it will return -1.0");
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 42fca9b..47f31d5 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -486,9 +486,7 @@
             return;
         }
         final BluetoothDevice btDevice = deviceList.get(0);
-        final @BluetoothProfile.BtProfileState int state =
-                proxy.getConnectionState(btDevice);
-        if (state == BluetoothProfile.STATE_CONNECTED) {
+        if (proxy.getConnectionState(btDevice) == BluetoothProfile.STATE_CONNECTED) {
             mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
                     new AudioDeviceBroker.BtDeviceChangedData(btDevice, null,
                         new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener"));
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 0da6a1b..79705a3 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -53,6 +53,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -64,6 +65,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.log.BiometricFrameworkStatsLogger;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -681,16 +683,18 @@
                         + ", Latency: " + latency);
             }
 
-            FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
+            final OperationContext operationContext = new OperationContext();
+            operationContext.isCrypto = isCrypto();
+            BiometricFrameworkStatsLogger.getInstance().authenticate(
+                    operationContext,
                     statsModality(),
-                    mUserId,
-                    isCrypto(),
+                    BiometricsProtoEnums.ACTION_UNKNOWN,
                     BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
-                    mPreAuthInfo.confirmationRequested,
-                    FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
-                    latency,
                     mDebugEnabled,
-                    -1 /* sensorId */,
+                    latency,
+                    FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
+                    mPreAuthInfo.confirmationRequested,
+                    mUserId,
                     -1f /* ambientLightLux */);
         } else {
             final long latency = System.currentTimeMillis() - mStartTimeMs;
@@ -711,17 +715,18 @@
                         + ", Latency: " + latency);
             }
             // Auth canceled
-            FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
+            final OperationContext operationContext = new OperationContext();
+            operationContext.isCrypto = isCrypto();
+            BiometricFrameworkStatsLogger.getInstance().error(
+                    operationContext,
                     statsModality(),
-                    mUserId,
-                    isCrypto(),
                     BiometricsProtoEnums.ACTION_AUTHENTICATE,
                     BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
-                    error,
-                    0 /* vendorCode */,
                     mDebugEnabled,
                     latency,
-                    -1 /* sensorId */);
+                    error,
+                    0 /* vendorCode */,
+                    mUserId);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
new file mode 100644
index 0000000..c5e266f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.log;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.common.OperationContext;
+
+import java.util.function.Consumer;
+
+/**
+ * Cache for system state not directly related to biometric operations that is used for
+ * logging or optimizations.
+ */
+public interface BiometricContext {
+    /** Gets the context source from the system context. */
+    static BiometricContext getInstance(@NonNull Context context) {
+        return BiometricContextProvider.defaultProvider(context);
+    }
+
+    /** Update the given context with the most recent values and return it. */
+    OperationContext updateContext(@NonNull OperationContext operationContext,
+            boolean isCryptoOperation);
+
+    /** The session id for keyguard entry, if active, or null. */
+    @Nullable Integer getKeyguardEntrySessionId();
+
+    /** The session id for biometric prompt usage, if active, or null. */
+    @Nullable Integer getBiometricPromptSessionId();
+
+    /** If the display is in AOD. */
+    boolean isAoD();
+
+    /**
+     * Subscribe to context changes.
+     *
+     * @param context context that will be modified when changed
+     * @param consumer callback when the context is modified
+     */
+    void subscribe(@NonNull OperationContext context, @NonNull Consumer<OperationContext> consumer);
+
+    /** Unsubscribe from context changes. */
+    void unsubscribe(@NonNull OperationContext context);
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
new file mode 100644
index 0000000..70acaff
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.log;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
+import com.android.internal.statusbar.IStatusBarService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A default provider for {@link BiometricContext}.
+ */
+class BiometricContextProvider implements BiometricContext {
+
+    private static final String TAG = "BiometricContextProvider";
+
+    private static final int SESSION_TYPES =
+            StatusBarManager.SESSION_KEYGUARD | StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+
+    private static BiometricContextProvider sInstance;
+
+    static BiometricContextProvider defaultProvider(@NonNull Context context) {
+        synchronized (BiometricContextProvider.class) {
+            if (sInstance == null) {
+                try {
+                    sInstance = new BiometricContextProvider(
+                            new AmbientDisplayConfiguration(context),
+                            IStatusBarService.Stub.asInterface(ServiceManager.getServiceOrThrow(
+                                    Context.STATUS_BAR_SERVICE)), null /* handler */);
+                } catch (ServiceNotFoundException e) {
+                    throw new IllegalStateException("Failed to find required service", e);
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    @NonNull
+    private final Map<OperationContext, Consumer<OperationContext>> mSubscribers =
+            new ConcurrentHashMap<>();
+
+    @Nullable
+    private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>();
+
+    private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    private boolean mIsDozing = false;
+
+    @VisibleForTesting
+    BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
+            @NonNull IStatusBarService service, @Nullable Handler handler) {
+        mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+        try {
+            service.setBiometicContextListener(new IBiometricContextListener.Stub() {
+                @Override
+                public void onDozeChanged(boolean isDozing) {
+                    mIsDozing = isDozing;
+                    notifyChanged();
+                }
+
+                private void notifyChanged() {
+                    if (handler != null) {
+                        handler.post(() -> notifySubscribers());
+                    } else {
+                        notifySubscribers();
+                    }
+                }
+            });
+            service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
+                @Override
+                public void onSessionStarted(int sessionType, InstanceId instance) {
+                    mSession.put(sessionType, instance);
+                }
+
+                @Override
+                public void onSessionEnded(int sessionType, InstanceId instance) {
+                    final InstanceId id = mSession.remove(sessionType);
+                    if (id != null && instance != null && id.getId() != instance.getId()) {
+                        Slog.w(TAG, "session id mismatch");
+                    }
+                }
+            });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to register biometric context listener", e);
+        }
+    }
+
+    @Override
+    public OperationContext updateContext(@NonNull OperationContext operationContext,
+            boolean isCryptoOperation) {
+        operationContext.isAoD = isAoD();
+        operationContext.isCrypto = isCryptoOperation;
+        setFirstSessionId(operationContext);
+        return operationContext;
+    }
+
+    private void setFirstSessionId(@NonNull OperationContext operationContext) {
+        Integer sessionId = getKeyguardEntrySessionId();
+        if (sessionId != null) {
+            operationContext.id = sessionId;
+            operationContext.reason = OperationReason.KEYGUARD;
+            return;
+        }
+
+        sessionId = getBiometricPromptSessionId();
+        if (sessionId != null) {
+            operationContext.id = sessionId;
+            operationContext.reason = OperationReason.BIOMETRIC_PROMPT;
+            return;
+        }
+
+        operationContext.id = 0;
+        operationContext.reason = OperationReason.UNKNOWN;
+    }
+
+    @Nullable
+    @Override
+    public Integer getKeyguardEntrySessionId() {
+        final InstanceId id = mSession.get(StatusBarManager.SESSION_KEYGUARD);
+        return id != null ? id.getId() : null;
+    }
+
+    @Nullable
+    @Override
+    public Integer getBiometricPromptSessionId() {
+        final InstanceId id = mSession.get(StatusBarManager.SESSION_BIOMETRIC_PROMPT);
+        return id != null ? id.getId() : null;
+    }
+
+    @Override
+    public boolean isAoD() {
+        return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+    }
+
+    @Override
+    public void subscribe(@NonNull OperationContext context,
+            @NonNull Consumer<OperationContext> consumer) {
+        mSubscribers.put(context, consumer);
+    }
+
+    @Override
+    public void unsubscribe(@NonNull OperationContext context) {
+        mSubscribers.remove(context);
+    }
+
+    private void notifySubscribers() {
+        mSubscribers.forEach((context, consumer) -> {
+            context.isAoD = isAoD();
+            consumer.accept(context);
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index c3471bd..8965227 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -17,6 +17,8 @@
 package com.android.server.biometrics.log;
 
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
 import android.util.Slog;
 
 import com.android.internal.util.FrameworkStatsLog;
@@ -33,42 +35,49 @@
 
     private BiometricFrameworkStatsLogger() {}
 
+    /** Shared instance. */
     public static BiometricFrameworkStatsLogger getInstance() {
         return sInstance;
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_ACQUIRED}. */
-    public void acquired(
+    public void acquired(OperationContext operationContext,
             int statsModality, int statsAction, int statsClient, boolean isDebug,
-            int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) {
+            int acquiredInfo, int vendorCode, int targetUserId) {
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED,
                 statsModality,
                 targetUserId,
-                isCrypto,
+                operationContext.isCrypto,
                 statsAction,
                 statsClient,
                 acquiredInfo,
                 vendorCode,
                 isDebug,
-                -1 /* sensorId */);
+                -1 /* sensorId */,
+                operationContext.id,
+                sessionType(operationContext.reason),
+                operationContext.isAoD);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
-    public void authenticate(
+    public void authenticate(OperationContext operationContext,
             int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
-            boolean authenticated, int authState, boolean requireConfirmation, boolean isCrypto,
-            int targetUserId, boolean isBiometricPrompt, float ambientLightLux) {
+            int authState, boolean requireConfirmation,
+            int targetUserId, float ambientLightLux) {
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
                 statsModality,
                 targetUserId,
-                isCrypto,
+                operationContext.isCrypto,
                 statsClient,
                 requireConfirmation,
                 authState,
                 sanitizeLatency(latency),
                 isDebug,
                 -1 /* sensorId */,
-                ambientLightLux);
+                ambientLightLux,
+                operationContext.id,
+                sessionType(operationContext.reason),
+                operationContext.isAoD);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
@@ -84,20 +93,23 @@
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */
-    public void error(
+    public void error(OperationContext operationContext,
             int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
-            int error, int vendorCode, boolean isCrypto, int targetUserId) {
+            int error, int vendorCode, int targetUserId) {
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
                 statsModality,
                 targetUserId,
-                isCrypto,
+                operationContext.isCrypto,
                 statsAction,
                 statsClient,
                 error,
                 vendorCode,
                 isDebug,
                 sanitizeLatency(latency),
-                -1 /* sensorId */);
+                -1 /* sensorId */,
+                operationContext.id,
+                sessionType(operationContext.reason),
+                operationContext.isAoD);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
@@ -123,4 +135,14 @@
         }
         return latency;
     }
+
+    private static int sessionType(@OperationReason byte reason) {
+        if (reason == OperationReason.BIOMETRIC_PROMPT) {
+            return BiometricsProtoEnums.SESSION_TYPE_BIOMETRIC_PROMPT;
+        }
+        if (reason == OperationReason.KEYGUARD) {
+            return BiometricsProtoEnums.SESSION_TYPE_KEYGUARD_ENTRY;
+        }
+        return BiometricsProtoEnums.SESSION_TYPE_UNKNOWN;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index d029af3..2a8d9f1 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -25,6 +25,7 @@
 import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.util.Slog;
@@ -79,6 +80,12 @@
         }
     };
 
+    /** Get a new logger with all unknown fields (for operations that do not require logs). */
+    public static BiometricLogger ofUnknown(@NonNull Context context) {
+        return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+    }
+
     /**
      * @param context system_server context
      * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants.
@@ -103,6 +110,11 @@
         mSensorManager = sensorManager;
     }
 
+    /** Creates a new logger with the action replaced with the new action. */
+    public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
+        return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+    }
+
     /** Disable logging metrics and only log critical events, such as system health issues. */
     public void disableMetrics() {
         mShouldLogMetrics = false;
@@ -133,8 +145,8 @@
     }
 
     /** Log an acquisition event. */
-    public void logOnAcquired(Context context,
-            int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) {
+    public void logOnAcquired(Context context, OperationContext operationContext,
+            int acquiredInfo, int vendorCode, int targetUserId) {
         if (!mShouldLogMetrics) {
             return;
         }
@@ -154,7 +166,7 @@
         if (DEBUG) {
             Slog.v(TAG, "Acquired! Modality: " + mStatsModality
                     + ", User: " + targetUserId
-                    + ", IsCrypto: " + isCrypto
+                    + ", IsCrypto: " + operationContext.isCrypto
                     + ", Action: " + mStatsAction
                     + ", Client: " + mStatsClient
                     + ", AcquiredInfo: " + acquiredInfo
@@ -165,14 +177,14 @@
             return;
         }
 
-        mSink.acquired(mStatsModality, mStatsAction, mStatsClient,
+        mSink.acquired(operationContext, mStatsModality, mStatsAction, mStatsClient,
                 Utils.isDebugEnabled(context, targetUserId),
-                acquiredInfo, vendorCode, isCrypto, targetUserId);
+                acquiredInfo, vendorCode, targetUserId);
     }
 
     /** Log an error during an operation. */
-    public void logOnError(Context context,
-            int error, int vendorCode, boolean isCrypto, int targetUserId) {
+    public void logOnError(Context context, OperationContext operationContext,
+            int error, int vendorCode, int targetUserId) {
         if (!mShouldLogMetrics) {
             return;
         }
@@ -183,7 +195,7 @@
         if (DEBUG) {
             Slog.v(TAG, "Error! Modality: " + mStatsModality
                     + ", User: " + targetUserId
-                    + ", IsCrypto: " + isCrypto
+                    + ", IsCrypto: " + operationContext.isCrypto
                     + ", Action: " + mStatsAction
                     + ", Client: " + mStatsClient
                     + ", Error: " + error
@@ -197,14 +209,14 @@
             return;
         }
 
-        mSink.error(mStatsModality, mStatsAction, mStatsClient,
+        mSink.error(operationContext, mStatsModality, mStatsAction, mStatsClient,
                 Utils.isDebugEnabled(context, targetUserId), latency,
-                error, vendorCode, isCrypto, targetUserId);
+                error, vendorCode, targetUserId);
     }
 
     /** Log authentication attempt. */
-    public void logOnAuthenticated(Context context,
-            boolean authenticated, boolean requireConfirmation, boolean isCrypto,
+    public void logOnAuthenticated(Context context, OperationContext operationContext,
+            boolean authenticated, boolean requireConfirmation,
             int targetUserId, boolean isBiometricPrompt) {
         if (!mShouldLogMetrics) {
             return;
@@ -230,7 +242,7 @@
         if (DEBUG) {
             Slog.v(TAG, "Authenticated! Modality: " + mStatsModality
                     + ", User: " + targetUserId
-                    + ", IsCrypto: " + isCrypto
+                    + ", IsCrypto: " + operationContext.isCrypto
                     + ", Client: " + mStatsClient
                     + ", RequireConfirmation: " + requireConfirmation
                     + ", State: " + authState
@@ -244,10 +256,9 @@
             return;
         }
 
-        mSink.authenticate(mStatsModality, mStatsAction, mStatsClient,
+        mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
                 Utils.isDebugEnabled(context, targetUserId),
-                latency, authenticated, authState, requireConfirmation, isCrypto,
-                targetUserId, isBiometricPrompt, mLastAmbientLux);
+                latency, authState, requireConfirmation, targetUserId, mLastAmbientLux);
     }
 
     /** Log enrollment outcome. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 8b8103e..0f0032b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -29,6 +29,9 @@
 import android.os.Vibrator;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import java.util.function.Supplier;
 
 /**
@@ -57,9 +60,9 @@
     public AcquisitionClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int cookie, int sensorId, boolean shouldVibrate,
-            int statsModality, int statsAction, int statsClient) {
-        super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, statsModality,
-                statsAction, statsClient);
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId,
+                logger, biometricContext);
         mPowerManager = context.getSystemService(PowerManager.class);
         mShouldVibrate = shouldVibrate;
     }
@@ -107,8 +110,8 @@
         // that do not handle lockout under the HAL. In these cases, ensure that the framework only
         // sends errors once per ClientMonitor.
         if (mShouldSendErrorToClient) {
-            getLogger().logOnError(getContext(), errorCode, vendorCode,
-                    isCryptoOperation(), getTargetUserId());
+            getLogger().logOnError(getContext(), getOperationContext(),
+                    errorCode, vendorCode, getTargetUserId());
             try {
                 if (getListener() != null) {
                     mShouldSendErrorToClient = false;
@@ -166,8 +169,8 @@
 
     protected final void onAcquiredInternal(int acquiredInfo, int vendorCode,
             boolean shouldSend) {
-        getLogger().logOnAcquired(getContext(), acquiredInfo, vendorCode,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnAcquired(getContext(), getOperationContext(),
+                acquiredInfo, vendorCode, getTargetUserId());
         if (DEBUG) {
             Slog.v(TAG, "Acquired: " + acquiredInfo + " " + vendorCode
                     + ", shouldSend: " + shouldSend);
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 b715faf..54b79e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -29,7 +29,6 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -39,6 +38,8 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -93,13 +94,13 @@
     public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int targetUserId, long operationId, boolean restricted, @NonNull String owner,
-            int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
-            int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
+            int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             boolean shouldVibrate, boolean isKeyguardBypassEnabled) {
         super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
-                shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE,
-                statsClient);
+                shouldVibrate, biometricLogger, biometricContext);
         mIsStrongBiometric = isStrongBiometric;
         mOperationId = operationId;
         mRequireConfirmation = requireConfirmation;
@@ -165,8 +166,8 @@
     @Override
     public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
             boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
-        getLogger().logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
-                isCryptoOperation(), getTargetUserId(), isBiometricPrompt());
+        getLogger().logOnAuthenticated(getContext(), getOperationContext(),
+                authenticated, mRequireConfirmation, getTargetUserId(), isBiometricPrompt());
 
         final ClientMonitorCallbackConverter listener = getListener();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index e1f7e2a..1b2e606 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -21,12 +21,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.NoSuchElementException;
@@ -50,6 +50,7 @@
     @NonNull private final String mOwner;
     private final int mSensorId; // sensorId as configured by the framework
     @NonNull private final BiometricLogger mLogger;
+    @NonNull private final BiometricContext mBiometricContext;
 
     @Nullable private IBinder mToken;
     private long mRequestId;
@@ -82,22 +83,13 @@
      * @param owner      name of the client that owns this
      * @param cookie     BiometricPrompt authentication cookie (to be moved into a subclass soon)
      * @param sensorId   ID of the sensor that the operation should be requested of
-     * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
-     * @param statsAction   One of {@link BiometricsProtoEnums} ACTION_* constants
-     * @param statsClient   One of {@link BiometricsProtoEnums} CLIENT_* constants
+     * @param logger     framework stats logger
+     * @param biometricContext system context metadata
      */
     public BaseClientMonitor(@NonNull Context context,
             @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
-            int statsClient) {
-        this(context, token, listener, userId, owner, cookie, sensorId,
-                new BiometricLogger(context, statsModality, statsAction, statsClient));
-    }
-
-    @VisibleForTesting
-    BaseClientMonitor(@NonNull Context context,
-            @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int cookie, int sensorId, @NonNull BiometricLogger logger) {
+            @NonNull String owner, int cookie, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         mSequentialId = sCount++;
         mContext = context;
         mToken = token;
@@ -108,6 +100,7 @@
         mCookie = cookie;
         mSensorId = sensorId;
         mLogger = logger;
+        mBiometricContext = biometricContext;
 
         try {
             if (token != null) {
@@ -207,20 +200,29 @@
         return false;
     }
 
+    /** System context that may change during operations. */
+    @NonNull
+    protected BiometricContext getBiometricContext() {
+        return mBiometricContext;
+    }
+
     /** Logger for this client */
     @NonNull
     public BiometricLogger getLogger() {
         return mLogger;
     }
 
+    @NonNull
     public final Context getContext() {
         return mContext;
     }
 
+    @NonNull
     public final String getOwnerString() {
         return mOwner;
     }
 
+    @Nullable
     public final ClientMonitorCallbackConverter getListener() {
         return mListener;
     }
@@ -229,6 +231,7 @@
         return mTargetUserId;
     }
 
+    @Nullable
     public final IBinder getToken() {
         return mToken;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 74f4931..483ce75 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -20,13 +20,14 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.Arrays;
 import java.util.function.Supplier;
@@ -53,10 +54,10 @@
     public EnrollClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
-            int timeoutSec, int statsModality, int sensorId, boolean shouldVibrate) {
+            int timeoutSec, int sensorId, boolean shouldVibrate,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_ENROLL,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                shouldVibrate, logger, biometricContext);
         mBiometricUtils = utils;
         mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
         mTimeoutSec = timeoutSec;
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 9689418..2adf0cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -18,12 +18,13 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.function.Supplier;
 
@@ -33,10 +34,10 @@
 
     public GenerateChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int userId, @NonNull String owner, int sensorId) {
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                biometricLogger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 66a1c6e..a6e8911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -19,9 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import java.util.function.Supplier;
 
 /**
@@ -33,6 +36,9 @@
     @NonNull
     protected final Supplier<T> mLazyDaemon;
 
+    @NonNull
+    private final OperationContext mOperationContext = new OperationContext();
+
     /**
      * @param context    system_server context
      * @param lazyDaemon pointer for lazy retrieval of the HAL
@@ -42,16 +48,15 @@
      * @param owner      name of the client that owns this
      * @param cookie     BiometricPrompt authentication cookie (to be moved into a subclass soon)
      * @param sensorId   ID of the sensor that the operation should be requested of
-     * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
-     * @param statsAction   One of {@link BiometricsProtoEnums} ACTION_* constants
-     * @param statsClient   One of {@link BiometricsProtoEnums} CLIENT_* constants
+     * @param biometricLogger framework stats logger
+     * @param biometricContext system context metadata
      */
     public HalClientMonitor(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
-            int statsClient) {
-        super(context, token, listener, userId, owner, cookie, sensorId, statsModality,
-                statsAction, statsClient);
+            @NonNull String owner, int cookie, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+        super(context, token, listener, userId, owner, cookie, sensorId,
+                biometricLogger, biometricContext);
         mLazyDaemon = lazyDaemon;
     }
 
@@ -71,4 +76,29 @@
      * {@link #start(ClientMonitorCallback)}.
      */
     public abstract void unableToStart();
+
+    @Override
+    public void destroy() {
+        super.destroy();
+
+        // subclasses should do this earlier in most cases, but ensure it happens now
+        unsubscribeBiometricContext();
+    }
+
+    protected OperationContext getOperationContext() {
+        return getBiometricContext().updateContext(mOperationContext, isCryptoOperation());
+    }
+
+    protected ClientMonitorCallback getBiometricContextUnsubscriber() {
+        return new ClientMonitorCallback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor monitor, boolean success) {
+                unsubscribeBiometricContext();
+            }
+        };
+    }
+
+    protected void unsubscribeBiometricContext() {
+        getBiometricContext().unsubscribe(mOperationContext);
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 0e6d11e..57ea812 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -19,11 +19,12 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -101,19 +102,22 @@
 
     protected abstract InternalEnumerateClient<T> getEnumerateClient(Context context,
             Supplier<T> lazyDaemon, IBinder token, int userId, String owner,
-            List<S> enrolledList, BiometricUtils<S> utils, int sensorId);
+            List<S> enrolledList, BiometricUtils<S> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext);
 
     protected abstract RemovalClient<S, T> getRemovalClient(Context context,
             Supplier<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner,
-            BiometricUtils<S> utils, int sensorId, Map<Integer, Long> authenticatorIds);
+            BiometricUtils<S> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            Map<Integer, Long> authenticatorIds);
 
     protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
-            int userId, @NonNull String owner, int sensorId, int statsModality,
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
-                userId, owner, 0 /* cookie */, sensorId, statsModality,
-                BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                userId, owner, 0 /* cookie */, sensorId, logger, biometricContext);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
         mEnrolledList = enrolledList;
@@ -127,7 +131,8 @@
         mUnknownHALTemplates.remove(template);
         mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(),
                 template.mIdentifier.getBiometricId(), template.mUserId,
-                getContext().getPackageName(), mBiometricUtils, getSensorId(), mAuthenticatorIds);
+                getContext().getPackageName(), mBiometricUtils, getSensorId(),
+                getLogger(), getBiometricContext(), mAuthenticatorIds);
 
         getLogger().logUnknownEnrollmentInHal();
 
@@ -145,7 +150,8 @@
 
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
         mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
-                getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
+                getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId(), getLogger(),
+                getBiometricContext());
 
         Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
         mCurrentTask.start(mEnumerateCallback);
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 5f97f37..7f8f38f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -19,11 +19,12 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,12 +48,14 @@
     protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, int userId, @NonNull String owner,
             @NonNull List<? extends BiometricAuthenticator.Identifier> enrolledList,
-            @NonNull BiometricUtils utils, int sensorId, int statsModality) {
+            @NonNull BiometricUtils utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         // Internal enumerate does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
-                0 /* cookie */, sensorId, statsModality, BiometricsProtoEnums.ACTION_ENUMERATE,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
+        //, BiometricsProtoEnums.ACTION_ENUMERATE,
+          //      BiometricsProtoEnums.CLIENT_UNKNOWN);
         mEnrolledList = enrolledList;
         mUtils = utils;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index 697d77c..d5aa5e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -19,12 +19,13 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.Map;
 import java.util.function.Supplier;
@@ -42,12 +43,13 @@
     @NonNull private final IInvalidationCallback mInvalidationCallback;
 
     public InvalidationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
-            int userId, int sensorId, @NonNull Map<Integer, Long> authenticatorIds,
+            int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull Map<Integer, Long> authenticatorIds,
             @NonNull IInvalidationCallback callback) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId,
                 context.getOpPackageName(), 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mAuthenticatorIds = authenticatorIds;
         mInvalidationCallback = callback;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
index b2661a2..1097bb7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
@@ -20,10 +20,11 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IInvalidationCallback;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 /**
  * ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other
@@ -74,11 +75,10 @@
     };
 
     public InvalidationRequesterClient(@NonNull Context context, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull BiometricUtils<S> utils) {
         super(context, null /* token */, null /* listener */, userId,
-                context.getOpPackageName(), 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                context.getOpPackageName(), 0 /* cookie */, sensorId, logger, biometricContext);
         mBiometricManager = context.getSystemService(BiometricManager.class);
         mUtils = utils;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index a0cef94..07ce841 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -19,12 +19,13 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.Map;
 import java.util.function.Supplier;
@@ -44,10 +45,12 @@
     public RemovalClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId,
-            @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                statsModality, BiometricsProtoEnums.ACTION_REMOVE,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
+        //, BiometricsProtoEnums.ACTION_REMOVE,
+          //      BiometricsProtoEnums.CLIENT_UNKNOWN);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
         mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 7d83863..88f4da2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -18,20 +18,21 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.function.Supplier;
 
 public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> {
 
     public RevokeChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
-            @NonNull IBinder token, int userId, @NonNull String owner, int sensorId) {
+            @NonNull IBinder token, int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 1bc3248..21c9f64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -19,11 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.function.Supplier;
 
@@ -47,10 +48,10 @@
 
     public StartUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStartedCallback<U> callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mUserStartedCallback = callback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index 3eafbb8..e8654dc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -19,11 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 
 import java.util.function.Supplier;
 
@@ -47,10 +48,10 @@
 
     public StopUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mUserStoppedCallback = callback;
     }
 
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 039b08e..2e82057 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
@@ -57,6 +57,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -645,7 +646,7 @@
                 try {
                     final SensorProps[] props = face.getSensorProps();
                     final FaceProvider provider = new FaceProvider(getContext(), props, instance,
-                            mLockoutResetDispatcher);
+                            mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
                     mServiceProviders.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index 006667a..29eee6b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -16,11 +16,11 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
+
 import android.annotation.NonNull;
 import android.hardware.biometrics.face.ISession;
 
-import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
-
 /**
  * A holder for an AIDL {@link ISession} with additional metadata about the current user
  * and the backend.
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 c4e0502..7765ab3 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
@@ -25,10 +25,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceManager;
@@ -37,7 +34,10 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 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.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -76,19 +76,36 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
-            boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
             boolean isKeyguardBypassEnabled) {
+        this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
+                restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+                isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
+                isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class));
+    }
+
+    @VisibleForTesting
+    FaceAuthenticationClient(@NonNull Context context,
+            @NonNull Supplier<AidlSession> lazyDaemon,
+            @NonNull IBinder token, long requestId,
+            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
+            boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, @NonNull UsageStats usageStats,
+            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+            boolean isKeyguardBypassEnabled, SensorPrivacyManager sensorPrivacyManager) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
-                lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
+                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+                isStrongBiometric, null /* taskStackListener */, lockoutCache,
+                allowBackgroundAuthentication, true /* shouldVibrate */,
                 isKeyguardBypassEnabled);
         setRequestId(requestId);
         mUsageStats = usageStats;
         mLockoutCache = lockoutCache;
         mNotificationManager = context.getSystemService(NotificationManager.class);
-        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mSensorPrivacyManager = sensorPrivacyManager;
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -138,13 +155,8 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().authenticateWithContext(mOperationId, context);
+            return session.getSession().authenticateWithContext(
+                    mOperationId, getOperationContext());
         } else {
             return session.getSession().authenticate(mOperationId);
         }
@@ -263,8 +275,8 @@
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
-        getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnError(getContext(), getOperationContext(),
+                error, 0 /* vendorCode */, getTargetUserId());
 
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -279,8 +291,8 @@
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
-        getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnError(getContext(), getOperationContext(),
+                error, 0 /* vendorCode */, getTargetUserId());
 
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 3f3db43..efedcf8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -21,15 +21,15 @@
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -52,13 +52,26 @@
     FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric) {
+        this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId,
+                logger, biometricContext, isStrongBiometric,
+                context.getSystemService(SensorPrivacyManager.class));
+    }
+
+    @VisibleForTesting
+    FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+            @NonNull IBinder token, long requestId,
+            @NonNull ClientMonitorCallbackConverter listener, int userId,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE,
-                BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+                true /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
-        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mSensorPrivacyManager = sensorPrivacyManager;
     }
 
     @Override
@@ -101,13 +114,7 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().detectInteractionWithContext(context);
+            return session.getSession().detectInteractionWithContext(getOperationContext());
         } else {
             return session.getSession().detectInteraction();
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 8dc53b6..da78536 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -20,10 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.face.EnrollmentType;
 import android.hardware.biometrics.face.Feature;
 import android.hardware.biometrics.face.IFace;
@@ -40,6 +37,8 @@
 import com.android.internal.R;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 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.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
@@ -89,11 +88,11 @@
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
-            @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
-            boolean debugConsent) {
+            @Nullable Surface previewSurface, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            int maxTemplatesPerUser, boolean debugConsent) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
-                timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
-                false /* shouldVibrate */);
+                timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mEnrollIgnoreList = getContext().getResources()
                 .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
@@ -199,14 +198,8 @@
                 HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
-            context.isCrypto = isCryptoOperation();
             return session.getSession().enrollWithContext(
-                    hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, context);
+                    hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, getOperationContext());
         } else {
             return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
                     mHwPreviewHandle);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index bdad268..165c3a2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -37,8 +39,10 @@
     FaceGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+                biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index 2f3187b..1f4f612 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -38,10 +39,10 @@
     FaceGetAuthenticatorIdClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             int userId, @NonNull String opPackageName, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FACE,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mAuthenticatorIds = authenticatorIds;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index 79479be..ef3b345 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -28,6 +27,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
@@ -48,10 +49,10 @@
 
     FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId) {
+            @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mUserId = userId;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index a2b0339..54f2033 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -39,29 +40,32 @@
 
     FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
-            @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+            @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
                 enrolledList, utils, authenticatorIds);
     }
 
     @Override
     protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
             Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
-            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
+            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId);
+                enrolledList, utils, sensorId, logger, biometricContext);
     }
 
     @Override
     protected RemovalClient<Face, AidlSession> getRemovalClient(Context context,
             Supplier<AidlSession> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         return new FaceRemovalClient(context, lazyDaemon, token,
                 null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
-                utils, sensorId, authenticatorIds);
+                utils, sensorId, logger, biometricContext, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
index 88c9d3b..d85455e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
@@ -40,9 +41,10 @@
     FaceInternalEnumerateClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Face> enrolledList,
-            @NonNull BiometricUtils<Face> utils, int sensorId) {
+            @NonNull BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                BiometricsProtoEnums.MODALITY_FACE);
+                logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
index 04ea2cfc..39d8de0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.InvalidationClient;
 
 import java.util.Map;
@@ -33,8 +35,10 @@
 
     public FaceInvalidationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
-        super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
+        super(context, lazyDaemon, userId, sensorId, logger, biometricContext,
+                authenticatorIds, callback);
     }
 
     @Override
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 9d7a552..4e03ee9 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
@@ -23,6 +23,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
@@ -47,6 +48,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 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.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -87,7 +90,7 @@
     @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;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -125,7 +128,8 @@
 
     public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext) {
         mContext = context;
         mHalInstanceName = halInstanceName;
         mSensors = new SparseArray<>();
@@ -134,6 +138,7 @@
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
+        mBiometricContext = biometricContext;
 
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
@@ -153,7 +158,7 @@
                     prop.supportsDetectInteraction, prop.halControlsPreview,
                     false /* resetLockoutRequiresChallenge */);
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, lockoutResetDispatcher);
+                    internalProp, lockoutResetDispatcher, mBiometricContext);
 
             mSensors.put(sensorId, sensor);
             Slog.d(getTag(), "Added: " + internalProp);
@@ -237,6 +242,9 @@
             final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
                     mContext, mSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext,
                     mSensors.get(sensorId).getAuthenticatorIds());
 
             scheduleForSensor(sensorId, client);
@@ -247,6 +255,8 @@
         mHandler.post(() -> {
             final InvalidationRequesterClient<Face> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
+                            BiometricLogger.ofUnknown(mContext),
+                            mBiometricContext,
                             FaceUtils.getInstance(sensorId));
             scheduleForSensor(sensorId, client);
         });
@@ -285,6 +295,9 @@
         mHandler.post(() -> {
             final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
                     mSensors.get(sensorId).getLazySession(), userId, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext,
                     mSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
         });
@@ -311,7 +324,10 @@
         mHandler.post(() -> {
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token,
-                    new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId);
+                    new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -322,7 +338,9 @@
         mHandler.post(() -> {
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token, userId, opPackageName, sensorId,
-                    challenge);
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -340,8 +358,10 @@
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
-                    ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
-                    debugConsent);
+                    ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, maxTemplatesPerUser, debugConsent);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -372,8 +392,9 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceDetectClient client = new FaceDetectClient(mContext,
                     mSensors.get(sensorId).getLazySession(),
-                    token, id, callback, userId, opPackageName,
-                    sensorId, isStrongBiometric, statsClient);
+                    token, id, callback, userId, opPackageName, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    mBiometricContext, isStrongBiometric);
             scheduleForSensor(sensorId, client);
         });
 
@@ -396,7 +417,9 @@
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
                     userId, operationId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+                    false /* requireConfirmation */, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    mBiometricContext, isStrongBiometric,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
                     allowBackgroundAuthentication, isKeyguardBypassEnabled);
             scheduleForSensor(sensorId, client);
@@ -450,6 +473,9 @@
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
                     opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext,
                     mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
         });
@@ -460,7 +486,10 @@
         mHandler.post(() -> {
             final FaceResetLockoutClient client = new FaceResetLockoutClient(
                     mContext, mSensors.get(sensorId).getLazySession(), userId,
-                    mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+                    mContext.getOpPackageName(), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, hardwareAuthToken,
                     mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
 
             scheduleForSensor(sensorId, client);
@@ -481,7 +510,9 @@
             final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId,
-                    mContext.getOpPackageName(), sensorId, feature, enabled, hardwareAuthToken);
+                    mContext.getOpPackageName(), sensorId,
+                    BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                    feature, enabled, hardwareAuthToken);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -498,7 +529,8 @@
             }
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token, callback, userId,
-                    mContext.getOpPackageName(), sensorId);
+                    mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
+                    mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -518,13 +550,21 @@
             final FaceInternalCleanupClient client =
                     new FaceInternalCleanupClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
-                            mContext.getOpPackageName(), sensorId, enrolledList,
+                            mContext.getOpPackageName(), sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            mBiometricContext, enrolledList,
                             FaceUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, callback);
         });
     }
 
+    private BiometricLogger createLogger(int statsAction, int statsClient) {
+        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+                statsAction, statsClient);
+    }
+
     @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 130a05a..0512017 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.RemovalClient;
@@ -44,9 +45,10 @@
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int[] biometricIds, int userId, @NonNull String owner,
             @NonNull BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+                logger, biometricContext, authenticatorIds);
         mBiometricIds = biometricIds;
     }
 
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 67bf3f5..de0a36a 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
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
@@ -26,6 +25,8 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -50,11 +51,11 @@
 
     FaceResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
         mLockoutCache = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index acd2e05..8838345 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
 import java.util.function.Supplier;
@@ -38,8 +40,10 @@
 
     FaceRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId, long challenge) {
-        super(context, lazyDaemon, token, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            long challenge) {
+        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
         mChallenge = challenge;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index 9d535a2..6c14387 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
@@ -27,6 +26,8 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
@@ -47,11 +48,11 @@
 
     FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId, int feature, boolean enabled,
-            byte[] hardwareAuthToken) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            int feature, boolean enabled, byte[] hardwareAuthToken) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mFeature = feature;
         mEnabled = enabled;
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
index f5a98ff..61e7ab7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -27,6 +27,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StartUserClient;
 
@@ -40,9 +42,10 @@
     public FaceStartUserClient(@NonNull Context context,
             @NonNull Supplier<IFace> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull ISessionCallback sessionCallback,
             @NonNull UserStartedCallback<ISession> callback) {
-        super(context, lazyDaemon, token, userId, sensorId, callback);
+        super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
         mSessionCallback = sessionCallback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
index 48b4856..0110ae9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StopUserClient;
 
@@ -33,8 +35,9 @@
 
     public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
-        super(context, lazyDaemon, token, userId, sensorId, callback);
+        super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
     }
 
     @Override
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 33e6fa4..b69c760 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
@@ -42,12 +42,15 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 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.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -88,7 +91,8 @@
     @NonNull private final Supplier<AidlSession> mLazySession;
     @Nullable private AidlSession mCurrentSession;
 
-    static class HalSessionCallback extends ISessionCallback.Stub {
+    @VisibleForTesting
+    public static class HalSessionCallback extends ISessionCallback.Stub {
         /**
          * Interface to sends results to the HalSessionCallback's owner.
          */
@@ -472,7 +476,8 @@
 
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
@@ -487,7 +492,9 @@
                     @Override
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
-                                mSensorProperties.sensorId, () -> mCurrentSession = null);
+                                mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                () -> mCurrentSession = null);
                     }
 
                     @NonNull
@@ -523,6 +530,7 @@
 
                         return new FaceStartUserClient(mContext, provider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), biometricContext,
                                 resultController, userStartedCallback);
                     }
                 });
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 586abe2..73c759f 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
@@ -55,6 +55,8 @@
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 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.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -117,6 +119,7 @@
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
     @Nullable private IBiometricsFace mDaemon;
     @NonNull private final HalResultController mHalResultController;
+    @NonNull private final BiometricContext mBiometricContext;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -153,6 +156,7 @@
         @NonNull private final LockoutHalImpl mLockoutTracker;
         @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
 
+
         HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
                 @NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
@@ -335,12 +339,14 @@
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull Handler handler,
-            @NonNull BiometricScheduler scheduler) {
+            @NonNull BiometricScheduler scheduler,
+            @NonNull BiometricContext biometricContext) {
         mSensorProperties = sensorProps;
         mContext = context;
         mSensorId = sensorProps.sensorId;
         mScheduler = scheduler;
         mHandler = handler;
+        mBiometricContext = biometricContext;
         mUsageStats = new UsageStats(context);
         mAuthenticatorIds = new HashMap<>();
         mLazyDaemon = Face10.this::getDaemon;
@@ -365,7 +371,8 @@
         final Handler handler = new Handler(Looper.getMainLooper());
         return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
                 new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
-                        null /* gestureAvailabilityTracker */));
+                        null /* gestureAvailabilityTracker */),
+                BiometricContext.getInstance(context));
     }
 
     @Override
@@ -533,7 +540,10 @@
 
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    opPackageName, mSensorId, sSystemClock.millis());
+                    opPackageName, mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, sSystemClock.millis());
             mGeneratedChallengeCache = client;
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -562,7 +572,10 @@
             mGeneratedChallengeCache = null;
 
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
-                    mLazyDaemon, token, userId, opPackageName, mSensorId);
+                    mLazyDaemon, token, userId, opPackageName, mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -590,7 +603,10 @@
             final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
-                    ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
+                    ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext);
 
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -637,8 +653,9 @@
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
                     mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
                     opPackageName, cookie, false /* requireConfirmation */, mSensorId,
-                    isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
-                    allowBackgroundAuthentication, isKeyguardBypassEnabled);
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    mBiometricContext, isStrongBiometric, mLockoutTracker,
+                    mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -670,7 +687,10 @@
 
             final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
-                    FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+                    FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -685,7 +705,10 @@
             final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
                     opPackageName,
-                    FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+                    FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -702,7 +725,9 @@
 
             final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
-                    hardwareAuthToken);
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, hardwareAuthToken);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -723,7 +748,9 @@
             final int faceId = faces.get(0).getBiometricId();
             final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId);
+                    opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
+                    mBiometricContext,
+                    feature, enabled, hardwareAuthToken, faceId);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -742,7 +769,9 @@
 
             final int faceId = faces.get(0).getBiometricId();
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
-                    token, listener, userId, opPackageName, mSensorId, feature, faceId);
+                    token, listener, userId, opPackageName, mSensorId,
+                    BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                    feature, faceId);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(
@@ -767,7 +796,10 @@
 
             final List<Face> enrolledList = getEnrolledFaces(mSensorId, userId);
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
-                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
+                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, enrolledList,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
         });
@@ -890,7 +922,9 @@
         final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty();
         final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
-                hasEnrolled, mAuthenticatorIds);
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN),
+                mBiometricContext, hasEnrolled, mAuthenticatorIds);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -904,6 +938,11 @@
         });
     }
 
+    private BiometricLogger createLogger(int statsAction, int statsClient) {
+        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+                statsAction, statsClient);
+    }
+
     /**
      * Sends a debug message to the HAL with the provided FileDescriptor and arguments.
      */
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 9038435..8d76e9f 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,7 +23,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -32,6 +31,8 @@
 
 import com.android.internal.R;
 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.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -66,12 +67,13 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
-            boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
             boolean isKeyguardBypassEnabled) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
+                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+                isStrongBiometric, null /* taskStackListener */,
                 lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
                 isKeyguardBypassEnabled);
         setRequestId(requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 92f7253..226e458 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.Status;
 import android.hardware.face.Face;
@@ -32,6 +31,8 @@
 
 import com.android.internal.R;
 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.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -58,10 +59,10 @@
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
-            @Nullable Surface previewSurface, int sensorId) {
+            @Nullable Surface previewSurface, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
-                false /* shouldVibrate */);
+                timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
         mEnrollIgnoreList = getContext().getResources()
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index b66ad60..97838a7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -25,6 +25,8 @@
 import android.util.Slog;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
@@ -51,8 +53,10 @@
     FaceGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId, long now) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, long now) {
+        super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+                biometricContext);
         mCreatedAt = now;
         mWaiting = new ArrayList<>();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 1b387bf..9812536 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.OptionalBool;
 import android.hardware.biometrics.face.V1_0.Status;
@@ -28,6 +27,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -48,13 +49,13 @@
 
     FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId, int feature, int faceId) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            int feature, int faceId) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mFeature = feature;
         mFaceId = faceId;
-
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index 93a2913..d21a750 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -40,29 +41,32 @@
 
     FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
-            @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+            @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
                 enrolledList, utils, authenticatorIds);
     }
 
     @Override
     protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context,
             Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
-            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
+            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId);
+                enrolledList, utils, sensorId, logger, biometricContext);
     }
 
     @Override
     protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context,
             Supplier<IBiometricsFace> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         return new FaceRemovalClient(context, lazyDaemon, token,
                 null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, authenticatorIds);
+                sensorId, logger, biometricContext, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
index f1788de..250dd7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
@@ -41,9 +42,10 @@
     FaceInternalEnumerateClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Face> enrolledList,
-            @NonNull BiometricUtils<Face> utils, int sensorId) {
+            @NonNull BiometricUtils<Face> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                BiometricsProtoEnums.MODALITY_FACE);
+                logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index cbc23e4..0ee7a35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.RemovalClient;
@@ -44,9 +45,11 @@
     FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
-            int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext,
+            @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger,
+                biometricContext, authenticatorIds);
         mBiometricId = biometricId;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index 88e2318..6e74d36 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -18,12 +18,13 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -42,10 +43,10 @@
 
     FaceResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull byte[] hardwareAuthToken) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
 
         mHardwareAuthToken = new ArrayList<>();
         for (byte b : hardwareAuthToken) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index ab8d161..b7b0dc04 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
 import java.util.function.Supplier;
@@ -37,8 +39,9 @@
 
     FaceRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId) {
-        super(context, lazyDaemon, token, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index b2b52e7..3c82f9c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.Status;
 import android.os.IBinder;
@@ -26,6 +25,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -48,11 +49,11 @@
 
     FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId, int feature, boolean enabled,
-            byte[] hardwareAuthToken, int faceId) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN);
+                logger, biometricContext);
         mFeature = feature;
         mEnabled = enabled;
         mFaceId = faceId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 04b9327..8385c3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.os.Environment;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -41,11 +42,11 @@
 
     FaceUpdateActiveUserClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, boolean hasEnrolledBiometrics,
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
         mAuthenticatorIds = authenticatorIds;
     }
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 6366e19..b4befd2 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
@@ -83,6 +83,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -815,7 +816,8 @@
                         UserHandle.USER_CURRENT) != 0) {
                     fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
                             mFingerprintStateCallback, hidlSensor,
-                            mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+                            mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
+                            BiometricContext.getInstance(getContext()));
                 } else {
                     fingerprint21 = Fingerprint21.newInstance(getContext(),
                             mFingerprintStateCallback, hidlSensor, mHandler,
@@ -843,7 +845,8 @@
                     final FingerprintProvider provider =
                             new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
                                     instance, mLockoutResetDispatcher,
-                                    mGestureAvailabilityDispatcher);
+                                    mGestureAvailabilityDispatcher,
+                                    BiometricContext.getInstance(getContext()));
                     mServiceProviders.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 727101a..55861bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -16,11 +16,11 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
+
 import android.annotation.NonNull;
 import android.hardware.biometrics.fingerprint.ISession;
 
-import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
-
 /**
  * A holder for an AIDL {@link ISession} with additional metadata about the current user
  * and the backend.
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 2c1c80c..d26a780 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
@@ -23,10 +23,8 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
@@ -35,6 +33,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
 import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.AuthenticationClient;
@@ -72,15 +72,18 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
-            int sensorId, boolean isStrongBiometric, int statsClient,
+            int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            boolean isStrongBiometric,
             @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
-                cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
+                cookie, requireConfirmation, sensorId,
+                biometricLogger, biometricContext,
+                isStrongBiometric, taskStackListener,
                 lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
                 false /* isKeyguardBypassEnabled */);
         setRequestId(requestId);
@@ -105,7 +108,8 @@
     @NonNull
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
-        return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
+        return new ClientMonitorCompositeCallback(mALSProbeCallback,
+                getBiometricContextUnsubscriber(), callback);
     }
 
     @Override
@@ -175,13 +179,17 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().authenticateWithContext(mOperationId, context);
+            final OperationContext opContext = getOperationContext();
+            final ICancellationSignal cancel =  session.getSession().authenticateWithContext(
+                    mOperationId, opContext);
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            });
+            return cancel;
         } else {
             return session.getSession().authenticate(mOperationId);
         }
@@ -190,6 +198,8 @@
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
+        unsubscribeBiometricContext();
+
         if (mCancellationSignal != null) {
             try {
                 mCancellationSignal.cancel();
@@ -219,7 +229,7 @@
                 context.y = y;
                 context.minor = minor;
                 context.major = major;
-                context.isAoD = false; // TODO; get value
+                context.isAoD = getBiometricContext().isAoD();
                 session.getSession().onPointerDownWithContext(context);
             } else {
                 session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
@@ -277,8 +287,8 @@
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
-        getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnError(getContext(), getOperationContext(),
+                error, 0 /* vendorCode */, getTargetUserId());
 
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -296,8 +306,8 @@
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
-        getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
-                isCryptoOperation(), getTargetUserId());
+        getLogger().logOnError(getContext(), getOperationContext(),
+                error, 0 /* vendorCode */, getTargetUserId());
 
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 6645332..0e89814 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -20,16 +20,15 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -54,11 +53,10 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId,
-            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric,
-            int statsClient) {
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+                true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
@@ -99,13 +97,7 @@
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().detectInteractionWithContext(context);
+            return session.getSession().detectInteractionWithContext(getOperationContext());
         } else {
             return session.getSession().detectInteraction();
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index d0c5bb8..e21d901 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -22,10 +22,8 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
@@ -38,6 +36,10 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -57,6 +59,7 @@
 
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
     @NonNull private final SensorOverlays mSensorOverlays;
+    @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
 
     private final @FingerprintManager.EnrollReason int mEnrollReason;
     @Nullable private ICancellationSignal mCancellationSignal;
@@ -68,19 +71,22 @@
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
-                !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
+                0 /* timeoutSec */, sensorId,
+                !sensorProps.isAnyUdfpsType() /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
+        mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
             getLogger().disableMetrics();
@@ -90,8 +96,8 @@
     @NonNull
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
-        return new ClientMonitorCompositeCallback(
-                getLogger().createALSCallback(true /* startWithClient */), callback);
+        return new ClientMonitorCompositeCallback(mALSProbeCallback,
+                getBiometricContextUnsubscriber(), callback);
     }
 
     @Override
@@ -140,22 +146,6 @@
     }
 
     @Override
-    protected void stopHalOperation() {
-        mSensorOverlays.hide(getSensorId());
-
-        if (mCancellationSignal != null) {
-            try {
-                mCancellationSignal.cancel();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when requesting cancel", e);
-                onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                        0 /* vendorCode */);
-                mCallback.onClientFinished(this, false /* success */);
-            }
-        }
-    }
-
-    @Override
     protected void startHalOperation() {
         mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
 
@@ -176,22 +166,44 @@
                 HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
 
         if (session.hasContextMethods()) {
-            final OperationContext context = new OperationContext();
-            // TODO: add reason, id, and isAoD
-            context.id = 0;
-            context.reason = OperationReason.UNKNOWN;
-            context.isAoD = false;
-            context.isCrypto = isCryptoOperation();
-            return session.getSession().enrollWithContext(hat, context);
+            final OperationContext opContext = getOperationContext();
+            final ICancellationSignal cancel = session.getSession().enrollWithContext(
+                    hat, opContext);
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            });
+            return cancel;
         } else {
             return session.getSession().enroll(hat);
         }
     }
 
     @Override
+    protected void stopHalOperation() {
+        mSensorOverlays.hide(getSensorId());
+        unsubscribeBiometricContext();
+
+        if (mCancellationSignal != null) {
+            try {
+                mCancellationSignal.cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when requesting cancel", e);
+                onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                        0 /* vendorCode */);
+                mCallback.onClientFinished(this, false /* success */);
+            }
+        }
+    }
+
+    @Override
     public void onPointerDown(int x, int y, float minor, float major) {
         try {
             mIsPointerDown = true;
+            mALSProbeCallback.getProbe().enable();
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
@@ -201,7 +213,7 @@
                 context.y = y;
                 context.minor = minor;
                 context.major = major;
-                context.isAoD = false;
+                context.isAoD = getBiometricContext().isAoD();
                 session.getSession().onPointerDownWithContext(context);
             } else {
                 session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
@@ -215,6 +227,7 @@
     public void onPointerUp() {
         try {
             mIsPointerDown = false;
+            mALSProbeCallback.getProbe().disable();
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 04a7ca0..ddae8be 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -38,8 +40,10 @@
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener,
-            int userId, @NonNull String owner, int sensorId) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, listener, userId, owner, sensorId,
+                biometricLogger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index 3a487fc..ea1a622 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -36,11 +37,12 @@
     private final Map<Integer, Long> mAuthenticatorIds;
 
     FingerprintGetAuthenticatorIdClient(@NonNull Context context,
-            @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, Map<Integer, Long> authenticatorIds) {
+            @NonNull Supplier<AidlSession> lazyDaemon,
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext);
         mAuthenticatorIds = authenticatorIds;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 0ecad72..09bdd6d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -22,6 +22,8 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -39,28 +41,35 @@
 class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
 
     FingerprintInternalCleanupClient(@NonNull Context context,
-            @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, @NonNull List<Fingerprint> enrolledList,
+            @NonNull Supplier<AidlSession> lazyDaemon,
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull List<Fingerprint> enrolledList,
             @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds);
+        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+                enrolledList, utils, authenticatorIds);
     }
 
     @Override
     protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
             Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
-            List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId) {
+            List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId);
+                enrolledList, utils, sensorId,
+                logger.swapAction(context, BiometricsProtoEnums.ACTION_ENUMERATE),
+                biometricContext);
     }
 
     @Override
     protected RemovalClient<Fingerprint, AidlSession> getRemovalClient(Context context,
             Supplier<AidlSession> lazyDaemon, IBinder token, int biometricId, int userId,
             String owner, BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         return new FingerprintRemovalClient(context, lazyDaemon, token,
                 null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
-                utils, sensorId, authenticatorIds);
+                utils, sensorId, logger.swapAction(context, BiometricsProtoEnums.ACTION_REMOVE),
+                biometricContext, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
index 06ba6d4..a5a832a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
@@ -18,12 +18,13 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
@@ -40,9 +41,10 @@
     protected FingerprintInternalEnumerateClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
-            @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
+            @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT);
+                logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
index 1ee32e9..bc02897 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.InvalidationClient;
 
 import java.util.Map;
@@ -33,8 +35,10 @@
 
     public FingerprintInvalidationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
-        super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
+        super(context, lazyDaemon, userId, sensorId, logger, biometricContext,
+                authenticatorIds, callback);
     }
 
     @Override
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 efc9304..f810bca 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
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.TypedArray;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
@@ -53,6 +54,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 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.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -98,7 +101,7 @@
     @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 IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
@@ -141,7 +144,8 @@
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull SensorProps[] props, @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
         mContext = context;
         mFingerprintStateCallback = fingerprintStateCallback;
         mHalInstanceName = halInstanceName;
@@ -150,6 +154,7 @@
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
+        mBiometricContext = biometricContext;
 
         final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
 
@@ -181,7 +186,8 @@
                                                     location.sensorRadius))
                                             .collect(Collectors.toList()));
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
+                    internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
+                    mBiometricContext);
 
             mSensors.put(sensorId, sensor);
             Slog.d(getTag(), "Added: " + internalProp);
@@ -298,6 +304,9 @@
                     new FingerprintGetAuthenticatorIdClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            mBiometricContext,
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
         });
@@ -307,6 +316,8 @@
         mHandler.post(() -> {
             final InvalidationRequesterClient<Fingerprint> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
+                            BiometricLogger.ofUnknown(mContext),
+                            mBiometricContext,
                             FingerprintUtils.getInstance(sensorId));
             scheduleForSensor(sensorId, client);
         });
@@ -317,7 +328,10 @@
         mHandler.post(() -> {
             final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
                     mContext, mSensors.get(sensorId).getLazySession(), userId,
-                    mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+                    mContext.getOpPackageName(), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, hardwareAuthToken,
                     mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
             scheduleForSensor(sensorId, client);
         });
@@ -331,7 +345,9 @@
                     new FingerprintGenerateChallengeClient(mContext,
                             mSensors.get(sensorId).getLazySession(), token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                            sensorId);
+                            sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -343,7 +359,10 @@
             final FingerprintRevokeChallengeClient client =
                     new FingerprintRevokeChallengeClient(mContext,
                             mSensors.get(sensorId).getLazySession(), token,
-                            userId, opPackageName, sensorId, challenge);
+                            userId, opPackageName, sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -361,6 +380,9 @@
                     mSensors.get(sensorId).getLazySession(), token, id,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext,
                     mSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@@ -399,8 +421,10 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
-                    opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
-                    statsClient);
+                    opPackageName, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    mBiometricContext,
+                    mUdfpsOverlayController, isStrongBiometric);
             scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
 
@@ -417,7 +441,9 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
                     userId, operationId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+                    false /* requireConfirmation */, sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
                     mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
                     mSensors.get(sensorId).getSensorProperties());
@@ -479,6 +505,9 @@
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext,
                     mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
@@ -492,14 +521,22 @@
             final FingerprintInternalCleanupClient client =
                     new FingerprintInternalCleanupClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
-                            mContext.getOpPackageName(), sensorId, enrolledList,
-                            FingerprintUtils.getInstance(sensorId),
+                            mContext.getOpPackageName(), sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            mBiometricContext,
+                            enrolledList, FingerprintUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
                     mFingerprintStateCallback));
         });
     }
 
+    private BiometricLogger createLogger(int statsAction, int statsClient) {
+        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
+                statsAction, statsClient);
+    }
+
     @Override
     public boolean isHardwareDetected(int sensorId) {
         return hasHalInstance();
@@ -524,6 +561,9 @@
             final FingerprintInvalidationClient client =
                     new FingerprintInvalidationClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId, sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            mBiometricContext,
                             mSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index fbc1dc0..d559bb1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -19,12 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.RemovalClient;
@@ -45,9 +46,10 @@
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+                logger, biometricContext, authenticatorIds);
         mBiometricIds = biometricIds;
     }
 
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 0e64dab..f90cba7 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
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
@@ -26,6 +25,8 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -50,11 +51,11 @@
 
     FingerprintResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext);
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
         mLockoutCache = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index fd93867..afa62e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
 import java.util.function.Supplier;
@@ -38,8 +40,10 @@
 
     FingerprintRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId, long challenge) {
-        super(context, lazyDaemon, token, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            long challenge) {
+        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
         mChallenge = challenge;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
index 9dc06e1..52305a3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -27,6 +27,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StartUserClient;
 
@@ -40,9 +42,10 @@
     public FingerprintStartUserClient(@NonNull Context context,
             @NonNull Supplier<IFingerprint> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull ISessionCallback sessionCallback,
             @NonNull UserStartedCallback<ISession> callback) {
-        super(context, lazyDaemon, token, userId, sensorId, callback);
+        super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
         mSessionCallback = sessionCallback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
index fac17f2c..2cc1879 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StopUserClient;
 
@@ -33,8 +35,10 @@
 
     public FingerprintStopUserClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId,
-            int sensorId, @NonNull UserStoppedCallback callback) {
-        super(context, lazyDaemon, token, userId, sensorId, callback);
+            int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull UserStoppedCallback callback) {
+        super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 2276232..63e345e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -39,12 +39,15 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 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.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -72,7 +75,7 @@
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} HAL.
  */
 @SuppressWarnings("deprecation")
-class Sensor {
+public class Sensor {
 
     private boolean mTestHalEnabled;
 
@@ -89,7 +92,8 @@
     @Nullable private AidlSession mCurrentSession;
     @NonNull private final Supplier<AidlSession> mLazySession;
 
-    static class HalSessionCallback extends ISessionCallback.Stub {
+    @VisibleForTesting
+    public static class HalSessionCallback extends ISessionCallback.Stub {
 
         /**
          * Interface to sends results to the HalSessionCallback's owner.
@@ -425,7 +429,8 @@
     Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
@@ -442,7 +447,9 @@
                     @Override
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new FingerprintStopUserClient(mContext, mLazySession, mToken,
-                                userId, mSensorProperties.sensorId, () -> mCurrentSession = null);
+                                userId, mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), biometricContext,
+                                () -> mCurrentSession = null);
                     }
 
                     @NonNull
@@ -478,6 +485,7 @@
 
                         return new FingerprintStartUserClient(mContext, provider::getHalInstance,
                                 mToken, newUserId, mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), biometricContext,
                                 resultController, userStartedCallback);
                     }
                 });
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 29d460f..9d60859 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
@@ -57,6 +57,8 @@
 import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
 import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
 import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
@@ -118,6 +120,7 @@
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
+    @NonNull private final BiometricContext mBiometricContext;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -318,15 +321,18 @@
         }
     }
 
+    @VisibleForTesting
     Fingerprint21(@NonNull Context context,
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull BiometricScheduler scheduler,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull HalResultController controller) {
+            @NonNull HalResultController controller,
+            @NonNull BiometricContext biometricContext) {
         mContext = context;
         mFingerprintStateCallback = fingerprintStateCallback;
+        mBiometricContext = biometricContext;
 
         mSensorProperties = sensorProps;
         mSensorId = sensorProps.sensorId;
@@ -368,7 +374,7 @@
         final HalResultController controller = new HalResultController(sensorProps.sensorId,
                 context, handler, scheduler);
         return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
-                lockoutResetDispatcher, controller);
+                lockoutResetDispatcher, controller, BiometricContext.getInstance(context));
     }
 
     @Override
@@ -493,6 +499,9 @@
         final FingerprintUpdateActiveUserClient client =
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                        mBiometricContext,
                         this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
@@ -536,7 +545,10 @@
         // thread.
         mHandler.post(() -> {
             final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
-                    userId, mContext.getOpPackageName(), sensorId, mLockoutTracker);
+                    userId, mContext.getOpPackageName(), sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, mLockoutTracker);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -548,7 +560,10 @@
             final FingerprintGenerateChallengeClient client =
                     new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                            mSensorProperties.sensorId);
+                            mSensorProperties.sensorId,
+                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -559,7 +574,10 @@
         mHandler.post(() -> {
             final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
                     mContext, mLazyDaemon, token, userId, opPackageName,
-                    mSensorProperties.sensorId);
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
     }
@@ -577,7 +595,11 @@
                     mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
                     userId, hardwareAuthToken, opPackageName,
                     FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
-                    mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext,
+                    mUdfpsOverlayController, mSidefpsController,
                     enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -616,8 +638,10 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mLazyDaemon, token, id, listener, userId, opPackageName,
-                    mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
-                    statsClient);
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    mBiometricContext, mUdfpsOverlayController,
+                    isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
 
@@ -636,7 +660,9 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
                     restricted, opPackageName, cookie, false /* requireConfirmation */,
-                    mSensorProperties.sensorId, isStrongBiometric, statsClient,
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
                     mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication, mSensorProperties);
@@ -678,7 +704,10 @@
             final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
                     userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
-                    mSensorProperties.sensorId, mAuthenticatorIds);
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
@@ -695,7 +724,10 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
                     0 /* fingerprintId */, userId, opPackageName,
                     FingerprintUtils.getLegacyInstance(mSensorId),
-                    mSensorProperties.sensorId, mAuthenticatorIds);
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
@@ -709,7 +741,10 @@
                     mSensorProperties.sensorId, userId);
             final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
-                    mSensorProperties.sensorId, enrolledList,
+                    mSensorProperties.sensorId,
+                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                    mBiometricContext, enrolledList,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
         });
@@ -722,6 +757,11 @@
                 mFingerprintStateCallback));
     }
 
+    private BiometricLogger createLogger(int statsAction, int statsClient) {
+        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
+                statsAction, statsClient);
+    }
+
     @Override
     public boolean isHardwareDetected(int sensorId) {
         return getDaemon() != null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 1694bd9..149526f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -37,6 +37,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.R;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -247,7 +248,8 @@
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext) {
         Slog.d(TAG, "Creating Fingerprint23Mock!");
 
         final Handler handler = new Handler(Looper.getMainLooper());
@@ -256,7 +258,7 @@
         final MockHalResultController controller =
                 new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
         return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
-                handler, lockoutResetDispatcher, controller);
+                handler, lockoutResetDispatcher, controller, biometricContext);
     }
 
     private static abstract class FakeFingerRunnable implements Runnable {
@@ -385,9 +387,10 @@
             @NonNull TestableBiometricScheduler scheduler,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull MockHalResultController controller) {
+            @NonNull MockHalResultController controller,
+            @NonNull BiometricContext biometricContext) {
         super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
-                lockoutResetDispatcher, controller);
+                lockoutResetDispatcher, controller, biometricContext);
         mScheduler = scheduler;
         mScheduler.init(this);
         mHandler = handler;
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 589bfcf..97fbb5f 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,7 +23,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
@@ -32,6 +31,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
 import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.AuthenticationClient;
@@ -69,7 +70,8 @@
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
-            int sensorId, boolean isStrongBiometric, int statsClient,
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
             @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutFrameworkImpl lockoutTracker,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
@@ -77,10 +79,9 @@
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
-                lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
-                false /* isKeyguardBypassEnabled */);
+                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+                isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
+                true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 8848746..c2929d0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -30,6 +29,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -59,11 +60,11 @@
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
-            boolean isStrongBiometric, int statsClient) {
+            int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+                true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
         mIsStrongBiometric = isStrongBiometric;
@@ -129,8 +130,9 @@
     @Override
     public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated,
             ArrayList<Byte> hardwareAuthToken) {
-        getLogger().logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */,
-                isCryptoOperation(), getTargetUserId(), false /* isBiometricPrompt */);
+        getLogger().logOnAuthenticated(getContext(), getOperationContext(),
+                authenticated, false /* requireConfirmation */,
+                getTargetUserId(), false /* isBiometricPrompt */);
 
         // Do not distinguish between success/failures.
         vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index c69deac..1d478e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
@@ -31,6 +30,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -62,12 +63,13 @@
             long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
-                true /* shouldVibrate */);
+                timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
+                biometricContext);
         setRequestId(requestId);
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index 591f542..3bb7135 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -41,8 +43,10 @@
     FingerprintGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+                biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index 403602b..5e7cf35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -18,11 +18,12 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -42,31 +43,35 @@
 
     FingerprintInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId, @NonNull List<Fingerprint> enrolledList,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull List<Fingerprint> enrolledList,
             @NonNull BiometricUtils<Fingerprint> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds);
+        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+                enrolledList, utils, authenticatorIds);
     }
 
     @Override
     protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient(
             Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
             int userId, String owner, List<Fingerprint> enrolledList,
-            BiometricUtils<Fingerprint> utils, int sensorId) {
+            BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId);
+                enrolledList, utils, sensorId, logger, biometricContext);
     }
 
     @Override
     protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context,
             Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils,
-            int sensorId, Map<Integer, Long> authenticatorIds) {
+            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         return new FingerprintRemovalClient(context, lazyDaemon, token,
                 null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, authenticatorIds);
+                sensorId, logger, biometricContext, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
index def8ed0..0840f1b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
@@ -42,9 +43,10 @@
     FingerprintInternalEnumerateClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
-            @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
+            @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                BiometricsProtoEnums.MODALITY_FINGERPRINT);
+                logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index 77c201c..9ec56c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -18,13 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.RemovalClient;
@@ -46,9 +47,10 @@
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+                logger, biometricContext, authenticatorIds);
         mBiometricId = biometricId;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
index ed28e3f..559ca06 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
@@ -18,9 +18,10 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 
@@ -33,10 +34,11 @@
     @NonNull final LockoutFrameworkImpl mLockoutTracker;
 
     public FingerprintResetLockoutClient(@NonNull Context context, int userId,
-            @NonNull String owner, int sensorId, @NonNull LockoutFrameworkImpl lockoutTracker) {
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull LockoutFrameworkImpl lockoutTracker) {
         super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */,
-                sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                sensorId, logger, biometricContext);
         mLockoutTracker = lockoutTracker;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
index 0180a46..6273417 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
 import java.util.function.Supplier;
@@ -39,8 +41,9 @@
 
     FingerprintRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId) {
-        super(context, lazyDaemon, token, userId, owner, sensorId);
+            int userId, @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index cb9c33e..a4e6025 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.os.Build;
 import android.os.Environment;
@@ -27,6 +26,8 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -50,12 +51,13 @@
 
     FingerprintUpdateActiveUserClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId, Supplier<Integer> currentUserId,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            Supplier<Integer> currentUserId,
             boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
             boolean forceUpdateAuthenticatorId) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                0 /* cookie */, sensorId, logger, biometricContext);
         mCurrentUserId = currentUserId;
         mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 1e00ea9..1b0341c 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -308,7 +308,8 @@
         public void onFixedRotationFinished(int displayId) { }
 
         @Override
-        public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { }
+        public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+                List<Rect> unrestricted) { }
     }
 
 
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 28508f4..53fe450 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -1090,6 +1090,10 @@
                 && mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
             return;
         }
+        if (mPm.checkPermission(Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION,
+                callingPackage) == PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
         // Don't notify if already notified for this uid and clip.
         if (clipboard.mNotifiedUids.get(uid)) {
             return;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index c2ca3a5..d0e39cc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -23,7 +23,6 @@
 import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
-import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -348,7 +347,7 @@
             mBaseState = Optional.of(baseState);
 
             if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
-                mOverrideRequestController.cancelOverrideRequests();
+                mOverrideRequestController.cancelOverrideRequest();
             }
             mOverrideRequestController.handleBaseStateChanged();
             updatePendingStateLocked();
@@ -503,7 +502,7 @@
             @OverrideRequestController.RequestStatus int status) {
         if (status == STATUS_ACTIVE) {
             mActiveOverride = Optional.of(request);
-        } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) {
+        } else if (status == STATUS_CANCELED) {
             if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
                 mActiveOverride = Optional.empty();
             }
@@ -528,8 +527,6 @@
                 // Schedule the notification now.
                 processRecord.notifyRequestActiveAsync(request.getToken());
             }
-        } else if (status == STATUS_SUSPENDED) {
-            processRecord.notifyRequestSuspendedAsync(request.getToken());
         } else {
             processRecord.notifyRequestCanceledAsync(request.getToken());
         }
@@ -595,15 +592,14 @@
         }
     }
 
-    private void cancelRequestInternal(int callingPid, @NonNull IBinder token) {
+    private void cancelStateRequestInternal(int callingPid) {
         synchronized (mLock) {
             final ProcessRecord processRecord = mProcessRecords.get(callingPid);
             if (processRecord == null) {
                 throw new IllegalStateException("Process " + callingPid
                         + " has no registered callback.");
             }
-
-            mOverrideRequestController.cancelRequest(token);
+            mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
         }
     }
 
@@ -628,6 +624,23 @@
         }
     }
 
+    /**
+     * Allow top processes to request or cancel a device state change. If the calling process ID is
+     * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
+     * @param callingPid
+     */
+    private void checkCanControlDeviceState(int callingPid) {
+        // Allow top processes to request a device state change
+        // If the calling process ID is not the top app, then we check if this process
+        // holds a permission to CONTROL_DEVICE_STATE
+        final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+        if (topApp == null || topApp.getPid() != callingPid) {
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "Permission required to request device state, "
+                            + "or the call must come from the top focused app.");
+        }
+    }
+
     private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
         @Override
         public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
@@ -716,24 +729,6 @@
             });
         }
 
-        public void notifyRequestSuspendedAsync(IBinder token) {
-            @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
-            if (lastStatus != null
-                    && (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) {
-                return;
-            }
-
-            mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
-            mHandler.post(() -> {
-                try {
-                    mCallback.onRequestSuspended(token);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
-                            ex);
-                }
-            });
-        }
-
         public void notifyRequestCanceledAsync(IBinder token) {
             @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
             if (lastStatus != null && lastStatus == STATUS_CANCELED) {
@@ -782,12 +777,7 @@
             // Allow top processes to request a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
-            final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
-            if (topApp.getPid() != callingPid) {
-                getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
-                        "Permission required to request device state, "
-                                + "or the call must come from the top focused app.");
-            }
+            checkCanControlDeviceState(callingPid);
 
             if (token == null) {
                 throw new IllegalArgumentException("Request token must not be null.");
@@ -802,25 +792,16 @@
         }
 
         @Override // Binder call
-        public void cancelRequest(IBinder token) {
+        public void cancelStateRequest() {
             final int callingPid = Binder.getCallingPid();
             // Allow top processes to cancel a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
-            final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
-            if (topApp.getPid() != callingPid) {
-                getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
-                        "Permission required to cancel device state, "
-                                + "or the call must come from the top focused app.");
-            }
-
-            if (token == null) {
-                throw new IllegalArgumentException("Request token must not be null.");
-            }
+            checkCanControlDeviceState(callingPid);
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                cancelRequestInternal(callingPid, token);
+                cancelStateRequestInternal(callingPid);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index eed68f8..659ee75 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -97,7 +97,7 @@
         try {
             if ("reset".equals(nextArg)) {
                 if (sLastRequest != null) {
-                    mClient.cancelRequest(sLastRequest);
+                    mClient.cancelStateRequest();
                     sLastRequest = null;
                 }
             } else {
@@ -105,9 +105,6 @@
                 DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build();
 
                 mClient.requestState(request, null /* executor */, null /* callback */);
-                if (sLastRequest != null) {
-                    mClient.cancelRequest(sLastRequest);
-                }
 
                 sLastRequest = request;
             }
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 36cb416..01f5a09 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -18,15 +18,13 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.IBinder;
+import android.util.Slog;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Manages the lifecycle of override requests.
@@ -36,29 +34,26 @@
  * <ul>
  *     <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the
  *     request will become suspended.</li>
- *     <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect
+ *     <li>The request is cancelled with {@link #cancelRequest} or as a side effect
  *     of other methods calls, such as {@link #handleProcessDied(int)}.</li>
  * </ul>
  */
 final class OverrideRequestController {
+    private static final String TAG = "OverrideRequestController";
+
     static final int STATUS_UNKNOWN = 0;
     /**
      * The request is the top-most request.
      */
     static final int STATUS_ACTIVE = 1;
     /**
-     * The request is still present but is being superseded by another request.
-     */
-    static final int STATUS_SUSPENDED = 2;
-    /**
      * The request is not longer valid.
      */
-    static final int STATUS_CANCELED = 3;
+    static final int STATUS_CANCELED = 2;
 
     @IntDef(prefix = {"STATUS_"}, value = {
             STATUS_UNKNOWN,
             STATUS_ACTIVE,
-            STATUS_SUSPENDED,
             STATUS_CANCELED
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -68,8 +63,6 @@
         switch (status) {
             case STATUS_ACTIVE:
                 return "ACTIVE";
-            case STATUS_SUSPENDED:
-                return "SUSPENDED";
             case STATUS_CANCELED:
                 return "CANCELED";
             case STATUS_UNKNOWN:
@@ -79,15 +72,13 @@
     }
 
     private final StatusChangeListener mListener;
-    private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>();
 
-    // List of override requests with the most recent override request at the end.
-    private final ArrayList<OverrideRequest> mRequests = new ArrayList<>();
+    // Handle to the current override request, null if none.
+    private OverrideRequest mRequest;
 
     private boolean mStickyRequestsAllowed;
-    // List of override requests that have outlived their process and will only be cancelled through
-    // a call to cancelStickyRequests().
-    private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>();
+    // The current request has outlived their process.
+    private boolean mStickyRequest;
 
     OverrideRequestController(@NonNull StatusChangeListener listener) {
         mListener = listener;
@@ -97,26 +88,26 @@
      * Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call
      * to {@link #handleProcessDied(int)} will not result in the request being cancelled
      * immediately. Instead, the request will be marked sticky and must be cancelled with a call
-     * to {@link #cancelStickyRequests()}.
+     * to {@link #cancelStickyRequest()}.
      */
     void setStickyRequestsAllowed(boolean stickyRequestsAllowed) {
         mStickyRequestsAllowed = stickyRequestsAllowed;
         if (!mStickyRequestsAllowed) {
-            cancelStickyRequests();
+            cancelStickyRequest();
         }
     }
 
     /**
-     * Adds a request to the top of the stack and notifies the listener of all changes to request
-     * status as a result of this operation.
+     * Sets the new request as active and cancels the previous override request, notifies the
+     * listener of all changes to request status as a result of this operation.
      */
     void addRequest(@NonNull OverrideRequest request) {
-        mRequests.add(request);
+        OverrideRequest previousRequest = mRequest;
+        mRequest = request;
         mListener.onStatusChanged(request, STATUS_ACTIVE);
 
-        if (mRequests.size() > 1) {
-            OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2);
-            mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED);
+        if (previousRequest != null) {
+            cancelRequestLocked(previousRequest);
         }
     }
 
@@ -124,42 +115,32 @@
      * Cancels the request with the specified {@code token} and notifies the listener of all changes
      * to request status as a result of this operation.
      */
-    void cancelRequest(@NonNull IBinder token) {
-        int index = getRequestIndex(token);
-        if (index == -1) {
+    void cancelRequest(@NonNull OverrideRequest request) {
+        // Either don't have a current request or attempting to cancel an already cancelled request
+        if (!hasRequest(request.getToken())) {
             return;
         }
-
-        OverrideRequest request = mRequests.remove(index);
-        if (index == mRequests.size() && mRequests.size() > 0) {
-            // We removed the current active request so we need to set the new active request
-            // before cancelling this request.
-            OverrideRequest newTop = getLast(mRequests);
-            mListener.onStatusChanged(newTop, STATUS_ACTIVE);
-        }
-        mListener.onStatusChanged(request, STATUS_CANCELED);
+        cancelCurrentRequestLocked();
     }
 
     /**
-     * Cancels all requests that are currently marked sticky and notifies the listener of all
+     * Cancels a request that is currently marked sticky and notifies the listener of all
      * changes to request status as a result of this operation.
      *
      * @see #setStickyRequestsAllowed(boolean)
      */
-    void cancelStickyRequests() {
-        mTmpRequestsToCancel.clear();
-        mTmpRequestsToCancel.addAll(mStickyRequests);
-        cancelRequestsLocked(mTmpRequestsToCancel);
+    void cancelStickyRequest() {
+        if (mStickyRequest) {
+            cancelCurrentRequestLocked();
+        }
     }
 
     /**
-     * Cancels all override requests, this could be due to the device being put
+     * Cancels the current override request, this could be due to the device being put
      * into a hardware state that declares the flag "FLAG_CANCEL_OVERRIDE_REQUESTS"
      */
-    void cancelOverrideRequests() {
-        mTmpRequestsToCancel.clear();
-        mTmpRequestsToCancel.addAll(mRequests);
-        cancelRequestsLocked(mTmpRequestsToCancel);
+    void cancelOverrideRequest() {
+        cancelCurrentRequestLocked();
     }
 
     /**
@@ -167,7 +148,7 @@
      * {@code token}, {@code false} otherwise.
      */
     boolean hasRequest(@NonNull IBinder token) {
-        return getRequestIndex(token) != -1;
+        return mRequest != null && token == mRequest.getToken();
     }
 
     /**
@@ -176,139 +157,79 @@
      * operation.
      */
     void handleProcessDied(int pid) {
-        if (mRequests.isEmpty()) {
+        if (mRequest == null) {
             return;
         }
 
-        mTmpRequestsToCancel.clear();
-        OverrideRequest prevActiveRequest = getLast(mRequests);
-        for (OverrideRequest request : mRequests) {
-            if (request.getPid() == pid) {
-                mTmpRequestsToCancel.add(request);
+        if (mRequest.getPid() == pid) {
+            if (mStickyRequestsAllowed) {
+                // Do not cancel the requests now because sticky requests are allowed. These
+                // requests will be cancelled on a call to cancelStickyRequests().
+                mStickyRequest = true;
+                return;
             }
+            cancelCurrentRequestLocked();
         }
-
-        if (mStickyRequestsAllowed) {
-            // Do not cancel the requests now because sticky requests are allowed. These
-            // requests will be cancelled on a call to cancelStickyRequests().
-            mStickyRequests.addAll(mTmpRequestsToCancel);
-            return;
-        }
-
-        cancelRequestsLocked(mTmpRequestsToCancel);
     }
 
     /**
      * Notifies the controller that the base state has changed. The controller will notify the
      * listener of all changes to request status as a result of this change.
-     *
-     * @return {@code true} if calling this method has lead to a new active request, {@code false}
-     * otherwise.
      */
-    boolean handleBaseStateChanged() {
-        if (mRequests.isEmpty()) {
-            return false;
+    void handleBaseStateChanged() {
+        if (mRequest == null) {
+            return;
         }
 
-        mTmpRequestsToCancel.clear();
-        OverrideRequest prevActiveRequest = getLast(mRequests);
-        for (int i = 0; i < mRequests.size(); i++) {
-            OverrideRequest request = mRequests.get(i);
-            if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
-                mTmpRequestsToCancel.add(request);
-            }
+        if ((mRequest.getFlags()
+                & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
+            cancelCurrentRequestLocked();
         }
-
-        final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
-        return newActiveRequest;
     }
 
     /**
      * Notifies the controller that the set of supported states has changed. The controller will
      * notify the listener of all changes to request status as a result of this change.
-     *
-     * @return {@code true} if calling this method has lead to a new active request, {@code false}
-     * otherwise.
      */
-    boolean handleNewSupportedStates(int[] newSupportedStates) {
-        if (mRequests.isEmpty()) {
-            return false;
+    void handleNewSupportedStates(int[] newSupportedStates) {
+        if (mRequest == null) {
+            return;
         }
 
-        mTmpRequestsToCancel.clear();
-        for (int i = 0; i < mRequests.size(); i++) {
-            OverrideRequest request = mRequests.get(i);
-            if (!contains(newSupportedStates, request.getRequestedState())) {
-                mTmpRequestsToCancel.add(request);
-            }
+        if (!contains(newSupportedStates, mRequest.getRequestedState())) {
+            cancelCurrentRequestLocked();
         }
-
-        final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
-        return newActiveRequest;
     }
 
     void dumpInternal(PrintWriter pw) {
-        final int requestCount = mRequests.size();
+        OverrideRequest overrideRequest = mRequest;
+        final boolean requestActive = overrideRequest != null;
         pw.println();
-        pw.println("Override requests: size=" + requestCount);
-        for (int i = 0; i < requestCount; i++) {
-            OverrideRequest overrideRequest = mRequests.get(i);
-            int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED;
-            pw.println("  " + i + ": mPid=" + overrideRequest.getPid()
+        pw.println("Override Request active: " + requestActive);
+        if (requestActive) {
+            pw.println("Request: mPid=" + overrideRequest.getPid()
                     + ", mRequestedState=" + overrideRequest.getRequestedState()
                     + ", mFlags=" + overrideRequest.getFlags()
-                    + ", mStatus=" + statusToString(status));
+                    + ", mStatus=" + statusToString(STATUS_ACTIVE));
         }
     }
 
+    private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
+        mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
+    }
+
     /**
-     * Handles cancelling a set of requests. If the set of requests to cancel will lead to a new
-     * request becoming active this request will also be notified of its change in state.
-     *
-     * @return {@code true} if calling this method has lead to a new active request, {@code false}
-     * otherwise.
+     * Handles cancelling {@code mRequest}.
+     * Notifies the listener of the canceled status as well.
      */
-    private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) {
-        if (requestsToCancel.isEmpty()) {
-            return false;
+    private void cancelCurrentRequestLocked() {
+        if (mRequest == null) {
+            Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
+            return;
         }
-
-        OverrideRequest prevActiveRequest = getLast(mRequests);
-        boolean causedNewRequestToBecomeActive = false;
-        mRequests.removeAll(requestsToCancel);
-        mStickyRequests.removeAll(requestsToCancel);
-        if (!mRequests.isEmpty()) {
-            OverrideRequest newActiveRequest = getLast(mRequests);
-            if (newActiveRequest != prevActiveRequest) {
-                mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
-                causedNewRequestToBecomeActive = true;
-            }
-        }
-
-        for (int i = 0; i < requestsToCancel.size(); i++) {
-            mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED);
-        }
-        return causedNewRequestToBecomeActive;
-    }
-
-    private int getRequestIndex(@NonNull IBinder token) {
-        final int numberOfRequests = mRequests.size();
-        if (numberOfRequests == 0) {
-            return -1;
-        }
-
-        for (int i = 0; i < numberOfRequests; i++) {
-            OverrideRequest request = mRequests.get(i);
-            if (request.getToken() == token) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    @Nullable
-    private static <T> T getLast(List<T> list) {
-        return list.size() > 0 ? list.get(list.size() - 1) : null;
+        mStickyRequest = false;
+        mListener.onStatusChanged(mRequest, STATUS_CANCELED);
+        mRequest = null;
     }
 
     private static boolean contains(int[] array, int value) {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 1b55257..162eb0e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -201,6 +201,10 @@
     // Controls High Brightness Mode.
     private HighBrightnessModeController mHbmController;
 
+    // Throttles (caps) maximum allowed brightness
+    private BrightnessThrottler mBrightnessThrottler;
+    private boolean mIsBrightnessThrottled;
+
     // Context-sensitive brightness configurations require keeping track of the foreground app's
     // package name and category, which is done by registering a TaskStackListener to call back to
     // us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's
@@ -226,7 +230,7 @@
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
             HysteresisLevels screenBrightnessThresholds, Context context,
-            HighBrightnessModeController hbmController,
+            HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
             BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
             int ambientLightHorizonLong) {
         this(new Injector(), callbacks, looper, sensorManager, lightSensor,
@@ -235,8 +239,8 @@
                 lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
                 darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
                 ambientBrightnessThresholds, screenBrightnessThresholds, context,
-                hbmController, idleModeBrightnessMapper, ambientLightHorizonShort,
-                ambientLightHorizonLong
+                hbmController, brightnessThrottler, idleModeBrightnessMapper,
+                ambientLightHorizonShort, ambientLightHorizonLong
         );
     }
 
@@ -249,7 +253,7 @@
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
             HysteresisLevels screenBrightnessThresholds, Context context,
-            HighBrightnessModeController hbmController,
+            HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
             BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
             int ambientLightHorizonLong) {
         mInjector = injector;
@@ -291,6 +295,7 @@
         mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mHbmController = hbmController;
+        mBrightnessThrottler = brightnessThrottler;
         mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
         mIdleModeBrightnessMapper = idleModeBrightnessMapper;
         // Initialize to active (normal) screen brightness mode
@@ -365,6 +370,13 @@
             prepareBrightnessAdjustmentSample();
         }
         changed |= setLightSensorEnabled(enable && !dozing);
+
+        if (mIsBrightnessThrottled != mBrightnessThrottler.isThrottled()) {
+            // Maximum brightness has changed, so recalculate display brightness.
+            mIsBrightnessThrottled = mBrightnessThrottler.isThrottled();
+            changed = true;
+        }
+
         if (changed) {
             updateAutoBrightness(false /*sendUpdate*/, userInitiatedChange);
         }
@@ -855,8 +867,11 @@
 
     // Clamps values with float range [0.0-1.0]
     private float clampScreenBrightness(float value) {
-        return MathUtils.constrain(value,
-                mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+        final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+                mBrightnessThrottler.getBrightnessCap());
+        final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+                mBrightnessThrottler.getBrightnessCap());
+        return MathUtils.constrain(value, minBrightness, maxBrightness);
     }
 
     private void prepareBrightnessAdjustmentSample() {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index d0ce9ef..5de162c 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -222,6 +222,22 @@
     }
 
     /**
+     * Returns the system preferred display mode.
+     */
+    public Display.Mode getSystemPreferredDisplayModeLocked() {
+        return EMPTY_DISPLAY_MODE;
+    }
+
+    /**
+     * Returns the display mode that was being used when this display was first found by
+     * display manager.
+     * @hide
+     */
+    public Display.Mode getActiveDisplayModeAtStartLocked() {
+        return EMPTY_DISPLAY_MODE;
+    }
+
+    /**
      * Sets the requested color mode.
      */
     public void setRequestedColorModeLocked(int colorMode) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 4e88acd..7f1482e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1833,6 +1833,16 @@
         }
     }
 
+    Display.Mode getSystemPreferredDisplayModeInternal(int displayId) {
+        synchronized (mSyncRoot) {
+            final DisplayDevice device = getDeviceForDisplayLocked(displayId);
+            if (device == null) {
+                return null;
+            }
+            return device.getSystemPreferredDisplayModeLocked();
+        }
+    }
+
     void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) {
         mDisplayModeDirector.setShouldAlwaysRespectAppRequestedMode(enabled);
     }
@@ -2183,6 +2193,16 @@
         }
     }
 
+    Display.Mode getActiveDisplayModeAtStart(int displayId) {
+        synchronized (mSyncRoot) {
+            final DisplayDevice device = getDeviceForDisplayLocked(displayId);
+            if (device == null) {
+                return null;
+            }
+            return device.getActiveDisplayModeAtStartLocked();
+        }
+    }
+
     void setAmbientColorTemperatureOverride(float cct) {
         synchronized (mSyncRoot) {
             final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
@@ -3471,6 +3491,16 @@
         }
 
         @Override // Binder call
+        public Display.Mode getSystemPreferredDisplayMode(int displayId) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getSystemPreferredDisplayModeInternal(displayId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index a9875c8..bfdac57 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -68,6 +68,8 @@
                 return clearUserPreferredDisplayMode();
             case "get-user-preferred-display-mode":
                 return getUserPreferredDisplayMode();
+            case "get-active-display-mode-at-start":
+                return getActiveDisplayModeAtStart();
             case "set-match-content-frame-rate-pref":
                 return setMatchContentFrameRateUserPreference();
             case "get-match-content-frame-rate-pref":
@@ -125,6 +127,9 @@
         pw.println("    Returns the user preferred display mode or null if no mode is set by user."
                 + "If DISPLAY_ID is passed, the mode for display with id = DISPLAY_ID is "
                 + "returned, else global display mode is returned.");
+        pw.println("  get-active-display-mode-at-start DISPLAY_ID");
+        pw.println("    Returns the display mode which was found at boot time of display with "
+                + "id = DISPLAY_ID");
         pw.println("  set-match-content-frame-rate-pref PREFERENCE");
         pw.println("    Sets the match content frame rate preference as PREFERENCE ");
         pw.println("  get-match-content-frame-rate-pref");
@@ -298,6 +303,30 @@
         return 0;
     }
 
+    private int getActiveDisplayModeAtStart() {
+        final String displayIdText = getNextArg();
+        if (displayIdText == null) {
+            getErrPrintWriter().println("Error: no displayId specified");
+            return 1;
+        }
+        final int displayId;
+        try {
+            displayId = Integer.parseInt(displayIdText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid displayId");
+            return 1;
+        }
+
+        Display.Mode mode = mService.getActiveDisplayModeAtStart(displayId);
+        if (mode == null) {
+            getOutPrintWriter().println("Boot display mode: null");
+            return 0;
+        }
+        getOutPrintWriter().println("Boot display mode: " + mode.getPhysicalWidth() + " "
+                + mode.getPhysicalHeight() + " " + mode.getRefreshRate());
+        return 0;
+    }
+
     private int setMatchContentFrameRateUserPreference() {
         final String matchContentFrameRatePrefText = getNextArg();
         if (matchContentFrameRatePrefText == null) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1f44854..6ae1a5a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -974,7 +974,7 @@
                     lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
                     darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
                     ambientBrightnessThresholds, screenBrightnessThresholds, mContext,
-                    mHbmController, mIdleModeBrightnessMapper,
+                    mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
                     mDisplayDeviceConfig.getAmbientHorizonShort(),
                     mDisplayDeviceConfig.getAmbientHorizonLong());
         } else {
@@ -1014,6 +1014,9 @@
                 mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
             }
         }
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+        }
     }
 
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1344,6 +1347,29 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
         }
 
+        // Now that a desired brightness has been calculated, apply brightness throttling. The
+        // dimming and low power transformations that follow can only dim brightness further.
+        //
+        // We didn't do this earlier through brightness clamping because we need to know both
+        // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations.
+        // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
+        // we broadcast this change through setting.
+        final float unthrottledBrightnessState = brightnessState;
+        if (mBrightnessThrottler.isThrottled()) {
+            brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
+            mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
+            if (!mAppliedThrottling) {
+                // Brightness throttling is needed, so do so quickly.
+                // Later, when throttling is removed, we let other mechanisms decide on speed.
+                slowChange = false;
+                updateScreenBrightnessSetting = true;
+            }
+            mAppliedThrottling = true;
+        } else if (mAppliedThrottling) {
+            mAppliedThrottling = false;
+            updateScreenBrightnessSetting = true;
+        }
+
         if (updateScreenBrightnessSetting) {
             // Tell the rest of the system about the new brightness in case we had to change it
             // for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -1390,20 +1416,6 @@
             mAppliedLowPower = false;
         }
 
-        // Apply brightness throttling after applying all other transforms
-        final float unthrottledBrightnessState = brightnessState;
-        if (mBrightnessThrottler.isThrottled()) {
-            brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
-            mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
-            if (!mAppliedThrottling) {
-                slowChange = false;
-            }
-            mAppliedThrottling = true;
-        } else if (mAppliedThrottling) {
-            slowChange = false;
-            mAppliedThrottling = false;
-        }
-
         // The current brightness to use has been calculated at this point, and HbmController should
         // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
         // here instead of having HbmController listen to the brightness setting because certain
@@ -1653,6 +1665,10 @@
 
     private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
         synchronized (mCachedBrightnessInfo) {
+            final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+                    mBrightnessThrottler.getBrightnessCap());
+            final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+                    mBrightnessThrottler.getBrightnessCap());
             boolean changed = false;
 
             changed |=
@@ -1663,10 +1679,10 @@
                         adjustedBrightness);
             changed |=
                 mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
-                        mHbmController.getCurrentBrightnessMin());
+                        minBrightness);
             changed |=
                 mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
-                        mHbmController.getCurrentBrightnessMax());
+                        maxBrightness);
             changed |=
                 mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
                         mHbmController.getHighBrightnessMode());
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 534ed5d..23c17f5 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -521,6 +521,10 @@
             } else if (mIsBlockedByLowPowerMode) {
                 reason = FrameworkStatsLog
                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
+            } else if (mBrightness <= mHbmData.transitionPoint) {
+                // This must be after external thermal check.
+                reason = FrameworkStatsLog
+                            .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS;
             }
         }
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 3a9ef0a..a31c231 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -192,8 +192,12 @@
         private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         private int mDefaultModeId = INVALID_MODE_ID;
+        private int mSystemPreferredModeId = INVALID_MODE_ID;
         private int mDefaultModeGroup;
         private int mUserPreferredModeId = INVALID_MODE_ID;
+        // This is used only for the purpose of testing, to verify if the mode was correct when the
+        // device started or booted.
+        private int mActiveDisplayModeAtStartId = INVALID_MODE_ID;
         private Display.Mode mUserPreferredMode;
         private int mActiveModeId = INVALID_MODE_ID;
         private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
@@ -208,7 +212,7 @@
         private boolean mSidekickActive;
         private SidekickInternal mSidekickInternal;
         private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
-        // The supported display modes according in SurfaceFlinger
+        // The supported display modes according to SurfaceFlinger
         private SurfaceControl.DisplayMode[] mSfDisplayModes;
         // The active display mode in SurfaceFlinger
         private SurfaceControl.DisplayMode mActiveSfDisplayMode;
@@ -230,6 +234,7 @@
             mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay,
                     mSurfaceControlProxy);
             mDisplayDeviceConfig = null;
+            mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
         }
 
         @Override
@@ -238,12 +243,23 @@
         }
 
         /**
+         * Returns the boot display mode of this display.
+         * @hide
+         */
+        @Override
+        public Display.Mode getActiveDisplayModeAtStartLocked() {
+            return findMode(mActiveDisplayModeAtStartId);
+        }
+
+        /**
          * Returns true if there is a change.
          **/
         public boolean updateDisplayPropertiesLocked(SurfaceControl.StaticDisplayInfo staticInfo,
                 SurfaceControl.DynamicDisplayInfo dynamicInfo,
                 SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
-            boolean changed = updateDisplayModesLocked(
+            boolean changed =
+                    updateSystemPreferredDisplayMode(dynamicInfo.preferredBootDisplayMode);
+            changed |= updateDisplayModesLocked(
                     dynamicInfo.supportedDisplayModes, dynamicInfo.activeDisplayModeId, modeSpecs);
             changed |= updateStaticInfo(staticInfo);
             changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
@@ -369,8 +385,11 @@
 
             // For a new display, we need to initialize the default mode ID.
             if (mDefaultModeId == INVALID_MODE_ID) {
-                mDefaultModeId = activeRecord.mMode.getModeId();
-                mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mDefaultModeId = mSystemPreferredModeId != INVALID_MODE_ID
+                        ? mSystemPreferredModeId : activeRecord.mMode.getModeId();
+                mDefaultModeGroup = mSystemPreferredModeId != INVALID_MODE_ID
+                        ? getModeById(mSfDisplayModes, mSystemPreferredModeId).group
+                        : mActiveSfDisplayMode.group;
             } else if (modesAdded && activeModeChanged) {
                 Slog.d(TAG, "New display modes are added and the active mode has changed, "
                         + "use active mode as default mode.");
@@ -531,6 +550,15 @@
             return true;
         }
 
+        private boolean updateSystemPreferredDisplayMode(int modeId) {
+            if (!mSurfaceControlProxy.getBootDisplayModeSupport()
+                    || mSystemPreferredModeId == modeId) {
+                return false;
+            }
+            mSystemPreferredModeId = modeId;
+            return true;
+        }
+
         private SurfaceControl.DisplayMode getModeById(SurfaceControl.DisplayMode[] supportedModes,
                 int modeId) {
             for (SurfaceControl.DisplayMode mode : supportedModes) {
@@ -857,6 +885,16 @@
             if (oldModeId != getPreferredModeId()) {
                 updateDeviceInfoLocked();
             }
+
+            if (!mSurfaceControlProxy.getBootDisplayModeSupport()) {
+                return;
+            }
+            if (mUserPreferredMode == null) {
+                mSurfaceControlProxy.clearBootDisplayMode(getDisplayTokenLocked());
+            } else {
+                mSurfaceControlProxy.setBootDisplayMode(getDisplayTokenLocked(),
+                        mUserPreferredMode.getModeId());
+            }
         }
 
         @Override
@@ -865,6 +903,11 @@
         }
 
         @Override
+        public Display.Mode getSystemPreferredDisplayModeLocked() {
+            return findMode(mSystemPreferredModeId);
+        }
+
+        @Override
         public void setRequestedColorModeLocked(int colorMode) {
             requestColorModeLocked(colorMode);
         }
@@ -1072,6 +1115,17 @@
             return matchingModeId;
         }
 
+        // Returns a mode with id = modeId.
+        private Display.Mode findMode(int modeId) {
+            for (int i = 0; i < mSupportedModes.size(); i++) {
+                Display.Mode supportedMode = mSupportedModes.valueAt(i).mMode;
+                if (supportedMode.getModeId() == modeId) {
+                    return supportedMode;
+                }
+            }
+            return null;
+        }
+
        // Returns a mode with resolution (width, height) and/or refreshRate. If any one of the
        // resolution or refresh-rate is valid, a mode having the valid parameters is returned.
         private Display.Mode findMode(int width, int height, float refreshRate) {
@@ -1318,6 +1372,18 @@
             return SurfaceControl.setActiveColorMode(displayToken, colorMode);
         }
 
+        public boolean getBootDisplayModeSupport() {
+            return SurfaceControl.getBootDisplayModeSupport();
+        }
+
+        public void setBootDisplayMode(IBinder displayToken, int modeId) {
+            SurfaceControl.setBootDisplayMode(displayToken, modeId);
+        }
+
+        public void clearBootDisplayMode(IBinder displayToken) {
+            SurfaceControl.clearBootDisplayMode(displayToken);
+        }
+
         public void setAutoLowLatencyMode(IBinder displayToken, boolean on) {
             SurfaceControl.setAutoLowLatencyMode(displayToken, on);
 
@@ -1340,7 +1406,6 @@
             return SurfaceControl.setDisplayBrightness(displayToken, sdrBacklight, sdrNits,
                     displayBacklight, displayNits);
         }
-
     }
 
     static class BacklightAdapter {
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 1ebd1f5a..d8672fc 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -70,7 +70,7 @@
                 mRate = 0;
                 mTargetValue = target;
                 mCurrentValue = target;
-                mProperty.setValue(mObject, target);
+                setPropertyValue(target);
                 if (mAnimating) {
                     mAnimating = false;
                     cancelAnimationCallback();
@@ -125,6 +125,15 @@
         mListener = listener;
     }
 
+    /**
+     * Sets the brightness property by converting the given value from HLG space
+     * into linear space.
+     */
+    private void setPropertyValue(float val) {
+        final float linearVal = BrightnessUtils.convertGammaToLinear(val);
+        mProperty.setValue(mObject, linearVal);
+    }
+
     private void postAnimationCallback() {
         mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null);
     }
@@ -160,9 +169,7 @@
             final float oldCurrentValue = mCurrentValue;
             mCurrentValue = mAnimatedValue;
             if (oldCurrentValue != mCurrentValue) {
-                // Convert value from HLG into linear space for the property.
-                final float linearCurrentVal = BrightnessUtils.convertGammaToLinear(mCurrentValue);
-                mProperty.setValue(mObject, linearCurrentVal);
+                setPropertyValue(mCurrentValue);
             }
             if (mTargetValue != mCurrentValue) {
                 postAnimationCallback();
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 151ec81..fb36dc7 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -44,26 +44,18 @@
         AmbientSensor.AmbientBrightnessSensor.Callbacks,
         AmbientSensor.AmbientColorTemperatureSensor.Callbacks {
 
-    protected static final String TAG = "DisplayWhiteBalanceController";
-    protected boolean mLoggingEnabled;
+    private static final String TAG = "DisplayWhiteBalanceController";
+    private boolean mLoggingEnabled;
 
-    private boolean mEnabled;
+    private final ColorDisplayServiceInternal mColorDisplayServiceInternal;
 
-    // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
-    // implements Callbacks and passes itself to the DWBC so it can call back into it without
-    // knowing about it.
-    private Callbacks mCallbacks;
-
-    private AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
-
+    private final AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
     @VisibleForTesting
     AmbientFilter mBrightnessFilter;
-    private AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
-
+    private final AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
     @VisibleForTesting
     AmbientFilter mColorTemperatureFilter;
-    private DisplayWhiteBalanceThrottler mThrottler;
-
+    private final DisplayWhiteBalanceThrottler mThrottler;
     // In low brightness conditions the ALS readings are more noisy and produce
     // high errors. This default is introduced to provide a fixed display color
     // temperature when sensor readings become unreliable.
@@ -74,16 +66,12 @@
     private final float mHighLightAmbientColorTemperature;
 
     private float mAmbientColorTemperature;
-
     @VisibleForTesting
     float mPendingAmbientColorTemperature;
     private float mLastAmbientColorTemperature;
 
-    private ColorDisplayServiceInternal mColorDisplayServiceInternal;
-
     // The most recent ambient color temperature values are kept for debugging purposes.
-    private static final int HISTORY_SIZE = 50;
-    private History mAmbientColorTemperatureHistory;
+    private final History mAmbientColorTemperatureHistory;
 
     // Override the ambient color temperature for debugging purposes.
     private float mAmbientColorTemperatureOverride;
@@ -91,6 +79,10 @@
     // A piecewise linear relationship between ambient and display color temperatures.
     private Spline.LinearSpline mAmbientToDisplayColorTemperatureSpline;
 
+    // A piecewise linear relationship between ambient and display color temperatures, with a
+    // stronger change between the two sets of values.
+    private Spline.LinearSpline mStrongAmbientToDisplayColorTemperatureSpline;
+
     // In very low or very high brightness conditions Display White Balance should
     // be to set to a default instead of using mAmbientToDisplayColorTemperatureSpline.
     // However, setting Display White Balance based on thresholds can cause the
@@ -109,6 +101,17 @@
     private float mLatestLowLightBias;
     private float mLatestHighLightBias;
 
+    private boolean mEnabled;
+
+    // Whether a higher-strength adjustment should be applied; this must be enabled in addition to
+    // mEnabled in order to be applied.
+    private boolean mStrongModeEnabled;
+
+    // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
+    // implements Callbacks and passes itself to the DWBC so it can call back into it without
+    // knowing about it.
+    private Callbacks mDisplayPowerControllerCallbacks;
+
     /**
      * @param brightnessSensor
      *      The sensor used to detect changes in the ambient brightness.
@@ -159,16 +162,18 @@
             @NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor,
             @NonNull AmbientFilter colorTemperatureFilter,
             @NonNull DisplayWhiteBalanceThrottler throttler,
-            float[] lowLightAmbientBrightnesses, float[] lowLightAmbientBiases,
+            float[] lowLightAmbientBrightnesses,
+            float[] lowLightAmbientBiases,
             float lowLightAmbientColorTemperature,
-            float[] highLightAmbientBrightnesses, float[] highLightAmbientBiases,
+            float[] highLightAmbientBrightnesses,
+            float[] highLightAmbientBiases,
             float highLightAmbientColorTemperature,
-            float[] ambientColorTemperatures, float[] displayColorTemperatures) {
+            float[] ambientColorTemperatures,
+            float[] displayColorTemperatures,
+            float[] strongAmbientColorTemperatures,
+            float[] strongDisplayColorTemperatures) {
         validateArguments(brightnessSensor, brightnessFilter, colorTemperatureSensor,
                 colorTemperatureFilter, throttler);
-        mLoggingEnabled = false;
-        mEnabled = false;
-        mCallbacks = null;
         mBrightnessSensor = brightnessSensor;
         mBrightnessFilter = brightnessFilter;
         mColorTemperatureSensor = colorTemperatureSensor;
@@ -179,7 +184,7 @@
         mAmbientColorTemperature = -1.0f;
         mPendingAmbientColorTemperature = -1.0f;
         mLastAmbientColorTemperature = -1.0f;
-        mAmbientColorTemperatureHistory = new History(HISTORY_SIZE);
+        mAmbientColorTemperatureHistory = new History(/* size= */ 50);
         mAmbientColorTemperatureOverride = -1.0f;
 
         try {
@@ -235,6 +240,13 @@
             mAmbientToDisplayColorTemperatureSpline = null;
         }
 
+        try {
+            mStrongAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline(
+                    strongAmbientColorTemperatures, strongDisplayColorTemperatures);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to create strong ambient to display color temperature spline", e);
+        }
+
         mColorDisplayServiceInternal = LocalServices.getService(ColorDisplayServiceInternal.class);
     }
 
@@ -255,6 +267,19 @@
     }
 
     /**
+     * Enable/disable the stronger adjustment option.
+     *
+     * @param enabled whether the stronger adjustment option should be turned on
+     */
+    public void setStrongModeEnabled(boolean enabled) {
+        mStrongModeEnabled = enabled;
+        if (mEnabled) {
+            updateAmbientColorTemperature();
+            updateDisplayColorTemperature();
+        }
+    }
+
+    /**
      * Set an object to call back to when the display color temperature should be updated.
      *
      * @param callbacks
@@ -263,10 +288,10 @@
      * @return Whether the method succeeded or not.
      */
     public boolean setCallbacks(Callbacks callbacks) {
-        if (mCallbacks == callbacks) {
+        if (mDisplayPowerControllerCallbacks == callbacks) {
             return false;
         }
-        mCallbacks = callbacks;
+        mDisplayPowerControllerCallbacks = callbacks;
         return true;
     }
 
@@ -321,7 +346,7 @@
         writer.println("DisplayWhiteBalanceController");
         writer.println("  mLoggingEnabled=" + mLoggingEnabled);
         writer.println("  mEnabled=" + mEnabled);
-        writer.println("  mCallbacks=" + mCallbacks);
+        writer.println("  mDisplayPowerControllerCallbacks=" + mDisplayPowerControllerCallbacks);
         mBrightnessSensor.dump(writer);
         mBrightnessFilter.dump(writer);
         mColorTemperatureSensor.dump(writer);
@@ -336,6 +361,8 @@
         writer.println("  mAmbientColorTemperatureOverride=" + mAmbientColorTemperatureOverride);
         writer.println("  mAmbientToDisplayColorTemperatureSpline="
                 + mAmbientToDisplayColorTemperatureSpline);
+        writer.println("  mStrongAmbientToDisplayColorTemperatureSpline="
+                + mStrongAmbientToDisplayColorTemperatureSpline);
         writer.println("  mLowLightAmbientBrightnessToBiasSpline="
                 + mLowLightAmbientBrightnessToBiasSpline);
         writer.println("  mHighLightAmbientBrightnessToBiasSpline="
@@ -364,9 +391,20 @@
         float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
         mLatestAmbientColorTemperature = ambientColorTemperature;
 
-        if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) {
-            ambientColorTemperature =
-                mAmbientToDisplayColorTemperatureSpline.interpolate(ambientColorTemperature);
+        if (mStrongModeEnabled) {
+            if (mStrongAmbientToDisplayColorTemperatureSpline != null
+                    && ambientColorTemperature != -1.0f) {
+                ambientColorTemperature =
+                        mStrongAmbientToDisplayColorTemperatureSpline.interpolate(
+                                ambientColorTemperature);
+            }
+        } else {
+            if (mAmbientToDisplayColorTemperatureSpline != null
+                    && ambientColorTemperature != -1.0f) {
+                ambientColorTemperature =
+                        mAmbientToDisplayColorTemperatureSpline.interpolate(
+                                ambientColorTemperature);
+            }
         }
 
         float ambientBrightness = mBrightnessFilter.getEstimate(time);
@@ -409,8 +447,8 @@
             Slog.d(TAG, "pending ambient color temperature: " + ambientColorTemperature);
         }
         mPendingAmbientColorTemperature = ambientColorTemperature;
-        if (mCallbacks != null) {
-            mCallbacks.updateWhiteBalance();
+        if (mDisplayPowerControllerCallbacks != null) {
+            mDisplayPowerControllerCallbacks.updateWhiteBalance();
         }
     }
 
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
index a72b1ed..07821b0 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -87,15 +87,22 @@
                 .config_displayWhiteBalanceHighLightAmbientColorTemperature);
         final float[] ambientColorTemperatures = getFloatArray(resources,
                 com.android.internal.R.array.config_displayWhiteBalanceAmbientColorTemperatures);
-        final float[] displayColorTempeartures = getFloatArray(resources,
+        final float[] displayColorTemperatures = getFloatArray(resources,
                 com.android.internal.R.array.config_displayWhiteBalanceDisplayColorTemperatures);
+        final float[] strongAmbientColorTemperatures = getFloatArray(resources,
+                com.android.internal.R.array
+                .config_displayWhiteBalanceStrongAmbientColorTemperatures);
+        final float[] strongDisplayColorTemperatures = getFloatArray(resources,
+                com.android.internal.R.array
+                .config_displayWhiteBalanceStrongDisplayColorTemperatures);
         final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController(
                 brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter,
                 throttler, displayWhiteBalanceLowLightAmbientBrightnesses,
                 displayWhiteBalanceLowLightAmbientBiases, lowLightAmbientColorTemperature,
                 displayWhiteBalanceHighLightAmbientBrightnesses,
                 displayWhiteBalanceHighLightAmbientBiases, highLightAmbientColorTemperature,
-                ambientColorTemperatures, displayColorTempeartures);
+                ambientColorTemperatures, displayColorTemperatures, strongAmbientColorTemperatures,
+                strongDisplayColorTemperatures);
         brightnessSensor.setCallbacks(controller);
         colorTemperatureSensor.setCallbacks(controller);
         return controller;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 611b288..f0a6af3 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -416,13 +416,14 @@
         mCurrentDreamCanDoze = canDoze;
         mCurrentDreamUserId = userId;
 
+        if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+            mUiEventLogger.log(DreamManagerEvent.DREAM_START);
+        }
+
         PowerManager.WakeLock wakeLock = mPowerManager
                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
         mHandler.post(wakeLock.wrap(() -> {
             mAtmInternal.notifyDreamStateChanged(true);
-            if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
-                mUiEventLogger.log(DreamManagerEvent.DREAM_START);
-            }
             mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock,
                     mDreamOverlayServiceName);
         }));
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 65ec1c0..79820a2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -29,6 +29,7 @@
 import android.os.SystemProperties;
 import android.provider.Settings.Global;
 import android.util.ArrayMap;
+import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -246,9 +247,11 @@
                 mAllowedValues.add(value);
                 if (mContext.getResources().getBoolean(defaultResId)) {
                     if (mDefaultValue != null) {
-                        throw new VerificationException("Invalid CEC setup for '"
-                            + this.getName() + "' setting. "
-                            + "Setting already has a default value.");
+                        Slog.e(TAG,
+                                "Failed to set '" + value + "' as a default for '" + this.getName()
+                                        + "': Setting already has a default ('" + mDefaultValue
+                                        + "').");
+                        return;
                     }
                     mDefaultValue = value;
                 }
@@ -277,6 +280,11 @@
         mContext = context;
         mStorageAdapter = storageAdapter;
 
+        // IMPORTANT: when adding a config value for a particular setting, register that value AFTER
+        // the existing values for that setting. That way, defaults set in the RRO are forward
+        // compatible even if the RRO doesn't include that new value yet
+        // (e.g. because it's ported from a previous release).
+
         Setting hdmiCecEnabled = registerSetting(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                 R.bool.config_cecHdmiCecEnabled_userConfigurable);
@@ -313,15 +321,15 @@
         powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV,
                 R.bool.config_cecPowerControlModeTv_allowed,
                 R.bool.config_cecPowerControlModeTv_default);
-        powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM,
-                R.bool.config_cecPowerControlModeTvAndAudioSystem_allowed,
-                R.bool.config_cecPowerControlModeTvAndAudioSystem_default);
         powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST,
                 R.bool.config_cecPowerControlModeBroadcast_allowed,
                 R.bool.config_cecPowerControlModeBroadcast_default);
         powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_NONE,
                 R.bool.config_cecPowerControlModeNone_allowed,
                 R.bool.config_cecPowerControlModeNone_default);
+        powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM,
+                R.bool.config_cecPowerControlModeTvAndAudioSystem_allowed,
+                R.bool.config_cecPowerControlModeTvAndAudioSystem_default);
 
         Setting powerStateChangeOnActiveSourceLost = registerSetting(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
@@ -385,6 +393,16 @@
                 R.bool.config_cecTvSendStandbyOnSleepDisabled_allowed,
                 R.bool.config_cecTvSendStandbyOnSleepDisabled_default);
 
+        Setting setMenuLanguage = registerSetting(
+                HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
+                R.bool.config_cecSetMenuLanguage_userConfigurable);
+        setMenuLanguage.registerValue(HdmiControlManager.SET_MENU_LANGUAGE_ENABLED,
+                R.bool.config_cecSetMenuLanguageEnabled_allowed,
+                R.bool.config_cecSetMenuLanguageEnabled_default);
+        setMenuLanguage.registerValue(HdmiControlManager.SET_MENU_LANGUAGE_DISABLED,
+                R.bool.config_cecSetMenuLanguageDisabled_allowed,
+                R.bool.config_cecSetMenuLanguageDisabled_default);
+
         Setting rcProfileTv = registerSetting(
                 HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
                 R.bool.config_cecRcProfileTv_userConfigurable);
@@ -697,6 +715,8 @@
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
                 return STORAGE_SHARED_PREFS;
+            case HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE:
+                return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
@@ -768,6 +788,8 @@
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
                 return setting.getName();
+            case HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE:
+                return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index c674ffe..454a76a 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -901,17 +901,12 @@
     protected int handleVendorCommandWithId(HdmiCecMessage message) {
         byte[] params = message.getParams();
         int vendorId = HdmiUtils.threeBytesToInt(params);
-        if (vendorId == mService.getVendorId()) {
-            if (!mService.invokeVendorCommandListenersOnReceived(
-                    mDeviceType, message.getSource(), message.getDestination(), params, true)) {
-                return Constants.ABORT_REFUSED;
-            }
-        } else if (message.getDestination() != Constants.ADDR_BROADCAST
-                && message.getSource() != Constants.ADDR_UNREGISTERED) {
-            Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
-            return Constants.ABORT_UNRECOGNIZED_OPCODE;
-        } else {
+        if (message.getDestination() == Constants.ADDR_BROADCAST
+                || message.getSource() == Constants.ADDR_UNREGISTERED) {
             Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
+        } else if (!mService.invokeVendorCommandListenersOnReceived(
+                mDeviceType, message.getSource(), message.getDestination(), params, true)) {
+            return Constants.ABORT_REFUSED;
         }
         return Constants.HANDLED;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index b23395f..0edcea5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -17,10 +17,15 @@
 package com.android.server.hdmi;
 
 import android.annotation.CallSuper;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
@@ -45,9 +50,6 @@
 public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
     private static final String TAG = "HdmiCecLocalDevicePlayback";
 
-    private static final boolean SET_MENU_LANGUAGE =
-            HdmiProperties.set_menu_language_enabled().orElse(false);
-
     // How long to wait after hotplug out before possibly going to Standby.
     @VisibleForTesting
     static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000;
@@ -383,7 +385,9 @@
     @Constants.HandleMessageResult
     protected int handleSetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (!SET_MENU_LANGUAGE) {
+        if (mService.getHdmiCecConfig().getIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE)
+                    == HdmiControlManager.SET_MENU_LANGUAGE_DISABLED) {
             return Constants.ABORT_UNRECOGNIZED_OPCODE;
         }
 
@@ -408,7 +412,7 @@
                     // locale from being chosen. 'eng' in the CEC command, for instance,
                     // will always be mapped to en-AU among other variants like en-US, en-GB,
                     // an en-IN, which may not be the expected one.
-                    LocalePicker.updateLocale(localeInfo.getLocale());
+                    startSetMenuLanguageActivity(localeInfo.getLocale());
                     return Constants.HANDLED;
                 }
             }
@@ -420,6 +424,24 @@
         }
     }
 
+    private void startSetMenuLanguageActivity(Locale locale) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Context context = mService.getContext();
+            Intent intent = new Intent();
+            intent.putExtra(HdmiControlManager.EXTRA_LOCALE, locale.toLanguageTag());
+            intent.setComponent(
+                    ComponentName.unflattenFromString(context.getResources().getString(
+                            com.android.internal.R.string.config_hdmiCecSetMenuLanguageActivity)));
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            context.startActivityAsUser(intent, context.getUser());
+        } catch (ActivityNotFoundException e) {
+            Slog.e(TAG, "unable to start HdmiCecSetMenuLanguageActivity");
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @Override
     @Constants.HandleMessageResult
     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
index 6f7473d..57fe9e6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
@@ -133,6 +133,7 @@
         addHandler(Constants.MESSAGE_SET_OSD_NAME, mBypasser);
         addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBypasser);
         addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBypasser);
+        addHandler(Constants.MESSAGE_GIVE_FEATURES, mBypasser);
 
         addHandler(Constants.MESSAGE_USER_CONTROL_PRESSED, mUserControlProcessedHandler);
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8391e0b..8ac2331 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1701,11 +1701,11 @@
 
     class VendorCommandListenerRecord implements IBinder.DeathRecipient {
         private final IHdmiVendorCommandListener mListener;
-        private final int mDeviceType;
+        private final int mVendorId;
 
-        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
+        VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int vendorId) {
             mListener = listener;
-            mDeviceType = deviceType;
+            mVendorId = vendorId;
         }
 
         @Override
@@ -2191,10 +2191,10 @@
         }
 
         @Override
-        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
-                final int deviceType) {
+        public void addVendorCommandListener(
+                final IHdmiVendorCommandListener listener, final int vendorId) {
             initBinderCall();
-            HdmiControlService.this.addVendorCommandListener(listener, deviceType);
+            HdmiControlService.this.addVendorCommandListener(listener, vendorId);
         }
 
         @Override
@@ -3354,8 +3354,9 @@
         mStandbyMessageReceived = false;
     }
 
-    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
-        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
+    @VisibleForTesting
+    void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {
+        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, vendorId);
         try {
             listener.asBinder().linkToDeath(record, 0);
         } catch (RemoteException e) {
@@ -3374,8 +3375,14 @@
                 return false;
             }
             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
-                if (record.mDeviceType != deviceType) {
-                    continue;
+                if (hasVendorId) {
+                    int vendorId =
+                            ((params[0] & 0xFF) << 16)
+                                    + ((params[1] & 0xFF) << 8)
+                                    + (params[2] & 0xFF);
+                    if (record.mVendorId != vendorId) {
+                        continue;
+                    }
                 }
                 try {
                     record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
new file mode 100644
index 0000000..dbd3f35
--- /dev/null
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.input;
+
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+
+import android.os.IBinder;
+import android.view.InputApplicationHandle;
+import android.view.InputChannel;
+import android.view.InputMonitor;
+import android.view.InputWindowHandle;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+/**
+ * An internal implementation of an {@link InputMonitor} that uses a spy window.
+ *
+ * This spy window is a layer container in the SurfaceFlinger hierarchy that does not have any
+ * graphical buffer, but can still receive input. It is parented to the DisplayContent so
+ * that it can spy on any pointer events that start in the DisplayContent bounds. When the
+ * object is constructed, it will add itself to SurfaceFlinger.
+ */
+class GestureMonitorSpyWindow {
+    final InputApplicationHandle mApplicationHandle;
+    final InputWindowHandle mWindowHandle;
+
+    // The token, InputChannel, and SurfaceControl are owned by this object.
+    final IBinder mMonitorToken;
+    final InputChannel mClientChannel;
+    final SurfaceControl mInputSurface;
+
+    GestureMonitorSpyWindow(IBinder token, String name, int displayId, int pid, int uid,
+            SurfaceControl sc, InputChannel inputChannel) {
+        mMonitorToken = token;
+        mClientChannel = inputChannel;
+        mInputSurface = sc;
+
+        mApplicationHandle = new InputApplicationHandle(null, name,
+                DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
+        mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
+
+        mWindowHandle.name = name;
+        mWindowHandle.token = mClientChannel.getToken();
+        mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+        mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+        mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+        mWindowHandle.visible = true;
+        mWindowHandle.focusable = false;
+        mWindowHandle.hasWallpaper = false;
+        mWindowHandle.paused = false;
+        mWindowHandle.ownerPid = pid;
+        mWindowHandle.ownerUid = uid;
+        mWindowHandle.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+        mWindowHandle.scaleFactor = 1.0f;
+        mWindowHandle.trustedOverlay = true;
+        mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.setInputWindowInfo(mInputSurface, mWindowHandle);
+        t.setLayer(mInputSurface, Integer.MAX_VALUE);
+        t.setPosition(mInputSurface, 0, 0);
+        t.setCrop(mInputSurface, null /* crop to parent surface */);
+        t.show(mInputSurface);
+
+        t.apply();
+    }
+
+    void remove() {
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.hide(mInputSurface);
+        t.remove(mInputSurface);
+        t.apply();
+
+        mClientChannel.dispose();
+    }
+
+    String dump() {
+        return "name='" + mWindowHandle.name + "', inputChannelToken="
+                + mClientChannel.getToken() + " displayId=" + mWindowHandle.displayId;
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index de933cc..bfaa7b3 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -150,6 +150,8 @@
     static final String TAG = "InputManager";
     static final boolean DEBUG = false;
 
+    private static final boolean USE_SPY_WINDOW_GESTURE_MONITORS = true;
+
     private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
     private static final String PORT_ASSOCIATIONS_PATH = "etc/input-port-associations.xml";
 
@@ -277,6 +279,12 @@
     @GuardedBy("mPointerDisplayIdLock")
     private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
 
+
+    // Holds all the registered gesture monitors that are implemented as spy windows. The spy
+    // windows are mapped by their InputChannel tokens.
+    @GuardedBy("mInputMonitors")
+    final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
+
     private static native long nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
     private static native void nativeStart(long ptr);
@@ -716,33 +724,77 @@
                 inputChannelName, Binder.getCallingPid());
     }
 
+    @NonNull
+    private InputChannel createSpyWindowGestureMonitor(IBinder monitorToken, String name,
+            int displayId, int pid, int uid) {
+        final SurfaceControl sc = mWindowManagerCallbacks.createSurfaceForGestureMonitor(name,
+                displayId);
+        if (sc == null) {
+            throw new IllegalArgumentException(
+                    "Could not create gesture monitor surface on display: " + displayId);
+        }
+        final InputChannel channel = createInputChannel(name);
+
+        try {
+            monitorToken.linkToDeath(() -> removeSpyWindowGestureMonitor(channel.getToken()), 0);
+        } catch (RemoteException e) {
+            Slog.i(TAG, "Client died before '" + name + "' could be created.");
+            return null;
+        }
+        synchronized (mInputMonitors) {
+            mInputMonitors.put(channel.getToken(),
+                    new GestureMonitorSpyWindow(monitorToken, name, displayId, pid, uid, sc,
+                            channel));
+        }
+
+        final InputChannel outInputChannel = new InputChannel();
+        channel.copyTo(outInputChannel);
+        return outInputChannel;
+    }
+
+    private void removeSpyWindowGestureMonitor(IBinder inputChannelToken) {
+        final GestureMonitorSpyWindow monitor;
+        synchronized (mInputMonitors) {
+            monitor = mInputMonitors.remove(inputChannelToken);
+        }
+        removeInputChannel(inputChannelToken);
+        if (monitor == null) return;
+        monitor.remove();
+    }
+
     /**
      * Creates an input monitor that will receive pointer events for the purposes of system-wide
      * gesture interpretation.
      *
-     * @param inputChannelName The input channel name.
+     * @param requestedName The input channel name.
      * @param displayId Target display id.
      * @return The input channel.
      */
     @Override // Binder call
-    public InputMonitor monitorGestureInput(String inputChannelName, int displayId) {
+    public InputMonitor monitorGestureInput(IBinder monitorToken, @NonNull String requestedName,
+            int displayId) {
         if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
-                "monitorInputRegion()")) {
+                "monitorGestureInput()")) {
             throw new SecurityException("Requires MONITOR_INPUT permission");
         }
-        Objects.requireNonNull(inputChannelName, "inputChannelName must not be null.");
+        Objects.requireNonNull(requestedName, "name must not be null.");
+        Objects.requireNonNull(monitorToken, "token must not be null.");
 
         if (displayId < Display.DEFAULT_DISPLAY) {
             throw new IllegalArgumentException("displayId must >= 0.");
         }
+        final String name = "[Gesture Monitor] " + requestedName;
         final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            InputChannel inputChannel = nativeCreateInputMonitor(
-                    mPtr, displayId, true /*isGestureMonitor*/, inputChannelName, pid);
-            InputMonitorHost host = new InputMonitorHost(inputChannel.getToken());
-            return new InputMonitor(inputChannel, host);
+            final InputChannel inputChannel =
+                    USE_SPY_WINDOW_GESTURE_MONITORS
+                            ? createSpyWindowGestureMonitor(monitorToken, name, displayId, pid, uid)
+                            : nativeCreateInputMonitor(mPtr, displayId, true /*isGestureMonitor*/,
+                                    requestedName, pid);
+            return new InputMonitor(inputChannel, new InputMonitorHost(inputChannel.getToken()));
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -2285,14 +2337,8 @@
         nativeNotifyPortAssociationsChanged(mPtr);
     }
 
-    /**
-     * Add a runtime association between the input device name and the display unique id.
-     * @param inputDeviceName The name of the input device.
-     * @param displayUniqueId The unique id of the associated display.
-     */
     @Override // Binder call
-    public void addUniqueIdAssociation(@NonNull String inputDeviceName,
-            @NonNull String displayUniqueId) {
+    public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
         if (!checkCallingPermission(
                 android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "addNameAssociation()")) {
@@ -2300,20 +2346,16 @@
                     "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
         }
 
-        Objects.requireNonNull(inputDeviceName);
+        Objects.requireNonNull(inputPort);
         Objects.requireNonNull(displayUniqueId);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.put(inputDeviceName, displayUniqueId);
+            mUniqueIdAssociations.put(inputPort, displayUniqueId);
         }
         nativeChangeUniqueIdAssociation(mPtr);
     }
 
-    /**
-     * Remove the runtime association between the input device and the display.
-     * @param inputDeviceName The port of the input device to be cleared.
-     */
     @Override // Binder call
-    public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+    public void removeUniqueIdAssociation(@NonNull String inputPort) {
         if (!checkCallingPermission(
                 android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "removeUniqueIdAssociation()")) {
@@ -2321,9 +2363,9 @@
                     "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
         }
 
-        Objects.requireNonNull(inputDeviceName);
+        Objects.requireNonNull(inputPort);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.remove(inputDeviceName);
+            mUniqueIdAssociations.remove(inputPort);
         }
         nativeChangeUniqueIdAssociation(mPtr);
     }
@@ -2573,27 +2615,48 @@
         String dumpStr = nativeDump(mPtr);
         if (dumpStr != null) {
             pw.println(dumpStr);
-            dumpAssociations(pw);
         }
+
+        pw.println("Input Manager Service (Java) State:");
+        dumpAssociations(pw, "  " /*prefix*/);
+        dumpSpyWindowGestureMonitors(pw, "  " /*prefix*/);
     }
 
-    private void dumpAssociations(PrintWriter pw) {
+    private void dumpAssociations(PrintWriter pw, String prefix) {
         if (!mStaticAssociations.isEmpty()) {
-            pw.println("Static Associations:");
+            pw.println(prefix + "Static Associations:");
             mStaticAssociations.forEach((k, v) -> {
-                pw.print("  port: " + k);
+                pw.print(prefix + "  port: " + k);
                 pw.println("  display: " + v);
             });
         }
 
         synchronized (mAssociationsLock) {
             if (!mRuntimeAssociations.isEmpty()) {
-                pw.println("Runtime Associations:");
+                pw.println(prefix + "Runtime Associations:");
                 mRuntimeAssociations.forEach((k, v) -> {
-                    pw.print("  port: " + k);
+                    pw.print(prefix + "  port: " + k);
                     pw.println("  display: " + v);
                 });
             }
+            if (!mUniqueIdAssociations.isEmpty()) {
+                pw.println(prefix + "Unique Id Associations:");
+                mUniqueIdAssociations.forEach((k, v) -> {
+                    pw.print(prefix + "  port: " + k);
+                    pw.println("  uniqueId: " + v);
+                });
+            }
+        }
+    }
+
+    private void dumpSpyWindowGestureMonitors(PrintWriter pw, String prefix) {
+        synchronized (mInputMonitors) {
+            if (mInputMonitors.isEmpty()) return;
+            pw.println(prefix + "Gesture Monitors (implemented as spy windows):");
+            int i = 0;
+            for (final GestureMonitorSpyWindow monitor : mInputMonitors.values()) {
+                pw.append(prefix + "  " + i++ + ": ").println(monitor.dump());
+            }
         }
     }
 
@@ -2621,6 +2684,7 @@
         synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
         synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
         synchronized (mPointerDisplayIdLock) { /* Test if blocked by pointer display id lock */ }
+        synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
         nativeMonitor(mPtr);
     }
 
@@ -2689,6 +2753,11 @@
 
     // Native callback.
     private void notifyInputChannelBroken(IBinder token) {
+        synchronized (mInputMonitors) {
+            if (mInputMonitors.containsKey(token)) {
+                removeSpyWindowGestureMonitor(token);
+            }
+        }
         mWindowManagerCallbacks.notifyInputChannelBroken(token);
     }
 
@@ -2724,6 +2793,17 @@
 
     // Native callback
     private void notifyWindowUnresponsive(IBinder token, String reason) {
+        int gestureMonitorPid = -1;
+        synchronized (mInputMonitors) {
+            final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
+            if (gestureMonitor != null) {
+                gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
+            }
+        }
+        if (gestureMonitorPid != -1) {
+            mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(gestureMonitorPid, reason);
+            return;
+        }
         mWindowManagerCallbacks.notifyWindowUnresponsive(token, reason);
     }
 
@@ -2734,6 +2814,17 @@
 
     // Native callback
     private void notifyWindowResponsive(IBinder token) {
+        int gestureMonitorPid = -1;
+        synchronized (mInputMonitors) {
+            final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
+            if (gestureMonitor != null) {
+                gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
+            }
+        }
+        if (gestureMonitorPid != -1) {
+            mWindowManagerCallbacks.notifyGestureMonitorResponsive(gestureMonitorPid);
+            return;
+        }
         mWindowManagerCallbacks.notifyWindowResponsive(token);
     }
 
@@ -3187,6 +3278,16 @@
          * pointers such as the mouse cursor and touch spots for the given display.
          */
         SurfaceControl getParentSurfaceForPointers(int displayId);
+
+        /**
+         * Create a {@link SurfaceControl} that can be configured to receive input over the entire
+         * display to implement a gesture monitor. The surface will not have a graphical buffer.
+         * @param name the name of the gesture monitor
+         * @param displayId the display to create the window in
+         * @return the SurfaceControl of the new layer container surface
+         */
+        @Nullable
+        SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
     }
 
     /**
@@ -3261,20 +3362,24 @@
      * Interface for the system to handle request from InputMonitors.
      */
     private final class InputMonitorHost extends IInputMonitorHost.Stub {
-        private final IBinder mToken;
+        private final IBinder mInputChannelToken;
 
-        InputMonitorHost(IBinder token) {
-            mToken = token;
+        InputMonitorHost(IBinder inputChannelToken) {
+            mInputChannelToken = inputChannelToken;
         }
 
         @Override
         public void pilferPointers() {
-            nativePilferPointers(mPtr, mToken);
+            nativePilferPointers(mPtr, mInputChannelToken);
         }
 
         @Override
         public void dispose() {
-            nativeRemoveInputChannel(mPtr, mToken);
+            if (USE_SPY_WINDOW_GESTURE_MONITORS) {
+                removeSpyWindowGestureMonitor(mInputChannelToken);
+                return;
+            }
+            nativeRemoveInputChannel(mPtr, mInputChannelToken);
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index 4c26166..9846a2b 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -19,7 +19,6 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
 import android.annotation.NonNull;
-import android.graphics.Rect;
 import android.os.Process;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
@@ -33,8 +32,11 @@
     public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName();
     static final boolean DEBUG = HandwritingModeController.DEBUG;
 
-    private final int mClientPid;
-    private final int mClientUid;
+    // Place the layer below the highest layer to place it under gesture monitors. If the surface
+    // is above gesture monitors, then edge-back and swipe-up gestures won't work when this surface
+    // is intercepting.
+    // TODO(b/217538817): Specify the ordering in WM by usage.
+    private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE - 1;
 
     private final InputApplicationHandle mApplicationHandle;
     private final InputWindowHandle mWindowHandle;
@@ -44,9 +46,6 @@
 
     HandwritingEventReceiverSurface(String name, int displayId, @NonNull SurfaceControl sc,
             @NonNull InputChannel inputChannel) {
-        // Initialized the window as being owned by the system.
-        mClientPid = Process.myPid();
-        mClientUid = Process.myUid();
         mApplicationHandle = new InputApplicationHandle(null, name,
                 DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
 
@@ -57,29 +56,26 @@
         mWindowHandle.name = name;
         mWindowHandle.token = mClientChannel.getToken();
         mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
-        mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+        mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
         mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
         mWindowHandle.visible = true;
         mWindowHandle.focusable = false;
         mWindowHandle.hasWallpaper = false;
         mWindowHandle.paused = false;
-        mWindowHandle.ownerPid = mClientPid;
-        mWindowHandle.ownerUid = mClientUid;
+        mWindowHandle.ownerPid = Process.myPid();
+        mWindowHandle.ownerUid = Process.myUid();
         mWindowHandle.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
                 | WindowManager.LayoutParams.INPUT_FEATURE_INTERCEPTS_STYLUS;
         mWindowHandle.scaleFactor = 1.0f;
         mWindowHandle.trustedOverlay = true;
-        mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface as crop */);
+        mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
-        t.setLayer(mInputSurface, Integer.MAX_VALUE);
+        t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER);
         t.setPosition(mInputSurface, 0, 0);
-        // Use an arbitrarily large crop that is positioned at the origin. The crop determines the
-        // bounds and the coordinate space of the input events, so it must start at the origin to
-        // receive input in display space.
-        // TODO(b/210039666): fix this in SurfaceFlinger and avoid the hack.
-        t.setCrop(mInputSurface, new Rect(0, 0, 10000, 10000));
+        t.setCrop(mInputSurface, null /* crop to parent surface */);
         t.show(mInputSurface);
         t.apply();
 
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index c87ca92..6cb3b3b 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -107,9 +107,11 @@
 
     @AnyThread
     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-            int configChanges, boolean stylusHwSupported) {
+            int configChanges, boolean stylusHwSupported,
+            boolean shouldShowImeSwitcherWhenImeIsShown) {
         try {
-            mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported);
+            mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported,
+                    shouldShowImeSwitcherWhenImeIsShown);
         } catch (RemoteException e) {
             logRemoteException(e);
         }
@@ -145,9 +147,20 @@
 
     @AnyThread
     void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
-            boolean restarting) {
+            boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) {
         try {
-            mTarget.startInput(startInputToken, inputContext, attribute, restarting);
+            mTarget.startInput(startInputToken, inputContext, attribute, restarting,
+                    shouldShowImeSwitcherWhenImeIsShown);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown) {
+        try {
+            mTarget.onShouldShowImeSwitcherWhenImeIsShownChanged(
+                    shouldShowImeSwitcherWhenImeIsShown);
         } catch (RemoteException e) {
             logRemoteException(e);
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 936b1a2..c207738a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2325,10 +2325,12 @@
                     true /* direct */);
         }
 
+        final boolean shouldShowImeSwitcherWhenImeIsShown =
+                shouldShowImeSwitcherWhenImeIsShownLocked();
         final SessionState session = mCurClient.curSession;
         setEnabledSessionLocked(session);
-        session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
-
+        session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting,
+                shouldShowImeSwitcherWhenImeIsShown);
         if (mShowRequested) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
             showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
@@ -2528,7 +2530,7 @@
                     + mCurTokenDisplayId);
         }
         inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
-                configChanges, supportStylusHw);
+                configChanges, supportStylusHw, shouldShowImeSwitcherWhenImeIsShownLocked());
     }
 
     @AnyThread
@@ -2731,6 +2733,12 @@
     }
 
     @GuardedBy("ImfLock.class")
+    boolean shouldShowImeSwitcherWhenImeIsShownLocked() {
+        return shouldShowImeSwitcherLocked(
+                InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE);
+    }
+
+    @GuardedBy("ImfLock.class")
     private boolean shouldShowImeSwitcherLocked(int visibility) {
         if (!mShowOngoingImeSwitcherForPhones) return false;
         if (mMenuController.getSwitchingDialogLocked() != null) return false;
@@ -2990,6 +2998,7 @@
         // the same enabled IMEs list.
         mSwitchingController.resetCircularListLocked(mContext);
 
+        sendShouldShowImeSwitcherWhenImeIsShownLocked();
     }
 
     @GuardedBy("ImfLock.class")
@@ -4308,6 +4317,7 @@
                 updateImeWindowStatus(msg.arg1 == 1);
                 return true;
             }
+
             // ---------------------------------------------------------
 
             case MSG_UNBIND_CLIENT:
@@ -4368,6 +4378,9 @@
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
                 mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
+                synchronized (ImfLock.class) {
+                    sendShouldShowImeSwitcherWhenImeIsShownLocked();
+                }
                 return true;
             case MSG_SYSTEM_UNLOCK_USER: {
                 final int userId = msg.arg1;
@@ -4638,6 +4651,8 @@
         // the same enabled IMEs list.
         mSwitchingController.resetCircularListLocked(mContext);
 
+        sendShouldShowImeSwitcherWhenImeIsShownLocked();
+
         // Notify InputMethodListListeners of the new installed InputMethods.
         final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList);
         mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
@@ -4645,6 +4660,17 @@
     }
 
     @GuardedBy("ImfLock.class")
+    void sendShouldShowImeSwitcherWhenImeIsShownLocked() {
+        final IInputMethodInvoker curMethod = mBindingController.getCurMethod();
+        if (curMethod == null) {
+            // No need to send the data if the IME is not yet bound.
+            return;
+        }
+        curMethod.onShouldShowImeSwitcherWhenImeIsShownChanged(
+                shouldShowImeSwitcherWhenImeIsShownLocked());
+    }
+
+    @GuardedBy("ImfLock.class")
     private void updateDefaultVoiceImeIfNeededLocked() {
         final String systemSpeechRecognizer =
                 mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 348bb2d..98bde11 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -203,6 +203,7 @@
             attrs.setTitle("Select input method");
             w.setAttributes(attrs);
             mService.updateSystemUiLocked();
+            mService.sendShouldShowImeSwitcherWhenImeIsShownLocked();
             mSwitchingDialog.show();
         }
     }
@@ -238,6 +239,7 @@
             mSwitchingDialogTitleView = null;
 
             mService.updateSystemUiLocked();
+            mService.sendShouldShowImeSwitcherWhenImeIsShownLocked();
             mDialogBuilder = null;
             mIms = null;
         }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 134fb96..01aee7b 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -31,13 +31,11 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.os.BestClock;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -61,7 +59,6 @@
 import java.nio.charset.StandardCharsets;
 import java.time.Clock;
 import java.time.Duration;
-import java.time.ZoneOffset;
 import java.util.HashMap;
 
 /**
@@ -97,15 +94,10 @@
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
             PackageManagerInternal pmInternal) {
-        this(localeManagerService.mContext, localeManagerService, pmInternal, getDefaultClock(),
+        this(localeManagerService.mContext, localeManagerService, pmInternal, Clock.systemUTC(),
                 new SparseArray<>());
     }
 
-    private static @NonNull Clock getDefaultClock() {
-        return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
-                Clock.systemUTC());
-    }
-
     @VisibleForTesting LocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
             PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index aa1fa9b..0c3f9f0 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.location;
 
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.app.compat.CompatChanges.isChangeEnabled;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -829,16 +830,12 @@
                         "only verified adas packages may use adas gnss bypass requests");
             }
             if (!isLocationProvider) {
-                mContext.enforceCallingOrSelfPermission(
-                        permission.WRITE_SECURE_SETTINGS,
-                        "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS);
+                LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
             }
         }
         if (request.isLocationSettingsIgnored()) {
             if (!isLocationProvider) {
-                mContext.enforceCallingOrSelfPermission(
-                        permission.WRITE_SECURE_SETTINGS,
-                        "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
+                LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
             }
         }
 
@@ -933,16 +930,12 @@
                         "only verified adas packages may use adas gnss bypass requests");
             }
             if (!isLocationProvider) {
-                mContext.enforceCallingOrSelfPermission(
-                        permission.WRITE_SECURE_SETTINGS,
-                        "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS);
+                LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
             }
         }
         if (request.isLocationSettingsIgnored()) {
             if (!isLocationProvider) {
-                mContext.enforceCallingOrSelfPermission(
-                        permission.WRITE_SECURE_SETTINGS,
-                        "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
+                LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
             }
         }
 
@@ -1202,7 +1195,7 @@
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, false, "setLocationEnabledForUser", null);
 
-        mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
+        mContext.enforceCallingOrSelfPermission(WRITE_SECURE_SETTINGS, null);
 
         LocationManager.invalidateLocalLocationEnabledCaches();
         mInjector.getSettingsHelper().setLocationEnabled(enabled, userId);
@@ -1220,7 +1213,7 @@
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, false, "setAdasGnssLocationEnabledForUser", null);
 
-        mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
+        LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
 
         mInjector.getLocationSettings().updateUserSettings(userId,
                 settings -> settings.withAdasGnssLocationEnabled(enabled));
diff --git a/services/core/java/com/android/server/location/LocationPermissions.java b/services/core/java/com/android/server/location/LocationPermissions.java
index 7528f8b..be702d9 100644
--- a/services/core/java/com/android/server/location/LocationPermissions.java
+++ b/services/core/java/com/android/server/location/LocationPermissions.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.annotation.IntDef;
@@ -121,6 +123,29 @@
     }
 
     /**
+     * Throws a security exception if the caller does not hold the required bypass permissions.
+     */
+    public static void enforceCallingOrSelfBypassPermission(Context context) {
+        enforceBypassPermission(context, Binder.getCallingUid(), Binder.getCallingPid());
+    }
+
+    /**
+     * Throws a security exception if the given uid/pid does not hold the required bypass
+     * perissions.
+     */
+    public static void enforceBypassPermission(Context context, int uid, int pid) {
+        if (context.checkPermission(WRITE_SECURE_SETTINGS, pid, uid) == PERMISSION_GRANTED) {
+            // TODO: disallow WRITE_SECURE_SETTINGS permission.
+            return;
+        }
+        if (context.checkPermission(LOCATION_BYPASS, pid, uid) == PERMISSION_GRANTED) {
+            return;
+        }
+        throw new SecurityException("uid" + uid + " does not have " + LOCATION_BYPASS
+                + "or " + WRITE_SECURE_SETTINGS + ".");
+    }
+
+    /**
      * Returns false if the caller does not hold the required location permissions.
      */
     public static boolean checkCallingOrSelfLocationPermission(Context context,
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index b65338d..9c85d18 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -69,6 +69,14 @@
                 handleSetAdasGnssLocationEnabled();
                 return 0;
             }
+            case "set-automotive-gnss-suspended": {
+                handleSetAutomotiveGnssSuspended();
+                return 0;
+            }
+            case "is-automotive-gnss-suspended": {
+                handleIsAutomotiveGnssSuspended();
+                return 0;
+            }
             case "providers": {
                 String command = getNextArgRequired();
                 return parseProvidersCommand(command);
@@ -189,6 +197,24 @@
         mService.setAdasGnssLocationEnabledForUser(enabled, userId);
     }
 
+    private void handleSetAutomotiveGnssSuspended() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            throw new IllegalStateException("command only recognized on automotive devices");
+        }
+
+        boolean suspended = Boolean.parseBoolean(getNextArgRequired());
+
+        mService.setAutomotiveGnssSuspended(suspended);
+    }
+
+    private void handleIsAutomotiveGnssSuspended() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            throw new IllegalStateException("command only recognized on automotive devices");
+        }
+
+        getOutPrintWriter().println(mService.isAutomotiveGnssSuspended());
+    }
+
     private void handleAddTestProvider() {
         String provider = getNextArgRequired();
 
@@ -359,6 +385,10 @@
             pw.println("  set-adas-gnss-location-enabled true|false [--user <USER_ID>]");
             pw.println("    Sets the ADAS GNSS location enabled state. If no user is specified,");
             pw.println("    the current user is assumed.");
+            pw.println("  is-automotive-gnss-suspended");
+            pw.println("    Gets the automotive GNSS suspended state.");
+            pw.println("  set-automotive-gnss-suspended true|false");
+            pw.println("    Sets the automotive GNSS suspended state.");
         }
         pw.println("  providers");
         pw.println("    The providers command is followed by a subcommand, as listed below:");
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 21ea1f6..a1ee46b 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -465,7 +465,7 @@
             try {
                 mHub.onHostEndpointConnected(info);
             } catch (RemoteException | ServiceSpecificException e) {
-                Log.e(TAG, "RemoteException in onHostEndpointConnected");
+                Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage());
             }
         }
 
@@ -474,7 +474,7 @@
             try {
                 mHub.onHostEndpointDisconnected((char) hostEndpointId);
             } catch (RemoteException | ServiceSpecificException e) {
-                Log.e(TAG, "RemoteException in onHostEndpointDisconnected");
+                Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage());
             }
         }
 
@@ -488,6 +488,8 @@
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            } catch (IllegalArgumentException e) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
         }
 
@@ -499,8 +501,10 @@
             try {
                 mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException | ServiceSpecificException e) {
+            } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            } catch (IllegalArgumentException e) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
         }
 
@@ -510,8 +514,10 @@
             try {
                 mHub.unloadNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException | ServiceSpecificException e) {
+            } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            } catch (IllegalArgumentException e) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
         }
 
@@ -521,8 +527,10 @@
             try {
                 mHub.enableNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException | ServiceSpecificException e) {
+            } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            } catch (IllegalArgumentException e) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
         }
 
@@ -532,8 +540,10 @@
             try {
                 mHub.disableNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException | ServiceSpecificException e) {
+            } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            } catch (IllegalArgumentException e) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
         }
 
@@ -542,8 +552,10 @@
             try {
                 mHub.queryNanoapps(contextHubId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException | ServiceSpecificException e) {
+            } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            } catch (IllegalArgumentException e) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
         }
 
@@ -551,7 +563,7 @@
             mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
             try {
                 mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
-            } catch (RemoteException | ServiceSpecificException e) {
+            } catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) {
                 Log.e(TAG, "Exception while registering callback: " + e.getMessage());
             }
         }
diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
index 5036a6e..bfef978 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
@@ -63,16 +63,25 @@
     @Override
     protected boolean registerWithService(Void ignored,
             Collection<GnssListenerRegistration> registrations) {
-        if (D) {
-            Log.d(TAG, "starting gnss nmea messages");
+        if (mGnssNative.startNmeaMessageCollection()) {
+            if (D) {
+                Log.d(TAG, "starting gnss nmea messages collection");
+            }
+            return true;
+        } else {
+            Log.e(TAG, "error starting gnss nmea messages collection");
+            return false;
         }
-        return true;
     }
 
     @Override
     protected void unregisterWithService() {
-        if (D) {
-            Log.d(TAG, "stopping gnss nmea messages");
+        if (mGnssNative.stopNmeaMessageCollection()) {
+            if (D) {
+                Log.d(TAG, "stopping gnss nmea messages collection");
+            }
+        } else {
+            Log.e(TAG, "error stopping gnss nmea messages collection");
         }
     }
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 936283d..0ce36d6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -43,6 +43,7 @@
 
     private final AppOpsHelper mAppOpsHelper;
     private final LocationUsageLogger mLogger;
+    private final GnssNative mGnssNative;
 
     private boolean mIsNavigating = false;
 
@@ -50,6 +51,7 @@
         super(injector);
         mAppOpsHelper = injector.getAppOpsHelper();
         mLogger = injector.getLocationUsageLogger();
+        mGnssNative = gnssNative;
 
         gnssNative.addBaseCallbacks(this);
         gnssNative.addStatusCallbacks(this);
@@ -64,16 +66,25 @@
     @Override
     protected boolean registerWithService(Void ignored,
             Collection<GnssListenerRegistration> registrations) {
-        if (D) {
-            Log.d(TAG, "starting gnss status");
+        if (mGnssNative.startSvStatusCollection()) {
+            if (D) {
+                Log.d(TAG, "starting gnss sv status");
+            }
+            return true;
+        } else {
+            Log.e(TAG, "error starting gnss sv status");
+            return false;
         }
-        return true;
     }
 
     @Override
     protected void unregisterWithService() {
-        if (D) {
-            Log.d(TAG, "stopping gnss status");
+        if (mGnssNative.stopSvStatusCollection()) {
+            if (D) {
+                Log.d(TAG, "stopping gnss sv status");
+            }
+        } else {
+            Log.e(TAG, "error stopping gnss sv status");
         }
     }
 
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 e072bf7..af87677 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
@@ -783,6 +783,38 @@
     }
 
     /**
+     * Starts sv status collection.
+     */
+    public boolean startSvStatusCollection() {
+        Preconditions.checkState(mRegistered);
+        return mGnssHal.startSvStatusCollection();
+    }
+
+    /**
+     * Stops sv status collection.
+     */
+    public boolean stopSvStatusCollection() {
+        Preconditions.checkState(mRegistered);
+        return mGnssHal.stopSvStatusCollection();
+    }
+
+    /**
+     * Starts NMEA message collection.
+     */
+    public boolean startNmeaMessageCollection() {
+        Preconditions.checkState(mRegistered);
+        return mGnssHal.startNmeaMessageCollection();
+    }
+
+    /**
+     * Stops NMEA message collection.
+     */
+    public boolean stopNmeaMessageCollection() {
+        Preconditions.checkState(mRegistered);
+        return mGnssHal.stopNmeaMessageCollection();
+    }
+
+    /**
      * Returns true if measurement corrections are supported.
      */
     public boolean isMeasurementCorrectionsSupported() {
@@ -1369,6 +1401,22 @@
             return native_inject_measurement_corrections(corrections);
         }
 
+        protected boolean startSvStatusCollection() {
+            return native_start_sv_status_collection();
+        }
+
+        protected boolean stopSvStatusCollection() {
+            return native_stop_sv_status_collection();
+        }
+
+        protected boolean startNmeaMessageCollection() {
+            return native_start_nmea_message_collection();
+        }
+
+        protected boolean stopNmeaMessageCollection() {
+            return native_stop_nmea_message_collection();
+        }
+
         protected int getBatchSize() {
             return native_get_batch_size();
         }
@@ -1478,6 +1526,10 @@
 
     private static native int native_read_nmea(byte[] buffer, int bufferSize);
 
+    private static native boolean native_start_nmea_message_collection();
+
+    private static native boolean native_stop_nmea_message_collection();
+
     // location injection APIs
 
     private static native void native_inject_location(
@@ -1501,6 +1553,11 @@
 
     private static native void native_inject_time(long time, long timeReference, int uncertainty);
 
+    // sv status APIs
+    private static native boolean native_start_sv_status_collection();
+
+    private static native boolean native_stop_sv_status_collection();
+
     // navigation message APIs
 
     private static native boolean native_is_navigation_message_supported();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8f05130..2d2edfa 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
+import static android.Manifest.permission.SET_INITIAL_LOCK;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
@@ -1650,9 +1651,13 @@
                     "This operation requires secure lock screen feature");
         }
         if (!hasPermission(PERMISSION) && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) {
-            throw new SecurityException(
-                    "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
-                            + PERMISSION);
+            if (hasPermission(SET_INITIAL_LOCK) && savedCredential.isNone()) {
+                // SET_INITIAL_LOCK can only be used if credential is not set.
+            } else {
+                throw new SecurityException(
+                        "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
+                                + PERMISSION);
+            }
         }
 
         final long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 303ab46..7f997df 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -677,9 +677,9 @@
         UserRecord userRecord = routerRecord.mUserRecord;
         userRecord.mRouterRecords.remove(routerRecord);
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+                obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
                         routerRecord.mUserRecord.mHandler,
-                        routerRecord.mPackageName, /* preferredFeatures=*/ null));
+                        routerRecord.mPackageName, null));
         userRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
                         userRecord.mHandler));
@@ -694,10 +694,10 @@
         }
         routerRecord.mDiscoveryPreference = discoveryRequest;
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+                obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
                         routerRecord.mUserRecord.mHandler,
                         routerRecord.mPackageName,
-                        routerRecord.mDiscoveryPreference.getPreferredFeatures()));
+                        routerRecord.mDiscoveryPreference));
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
                         routerRecord.mUserRecord.mHandler));
@@ -921,7 +921,7 @@
             // TODO: UserRecord <-> routerRecord, why do they reference each other?
             // How about removing mUserRecord from routerRecord?
             routerRecord.mUserRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManager,
+                    obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager,
                         routerRecord.mUserRecord.mHandler, routerRecord, manager));
         }
 
@@ -2118,19 +2118,19 @@
             }
         }
 
-        private void notifyPreferredFeaturesChangedToManager(@NonNull RouterRecord routerRecord,
+        private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
                 @NonNull IMediaRouter2Manager manager) {
             try {
-                manager.notifyPreferredFeaturesChanged(routerRecord.mPackageName,
-                        routerRecord.mDiscoveryPreference.getPreferredFeatures());
+                manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
+                        routerRecord.mDiscoveryPreference);
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify preferred features changed."
                         + " Manager probably died.", ex);
             }
         }
 
-        private void notifyPreferredFeaturesChangedToManagers(@NonNull String routerPackageName,
-                @Nullable List<String> preferredFeatures) {
+        private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
+                @Nullable RouteDiscoveryPreference discoveryPreference) {
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
                 return;
@@ -2143,7 +2143,8 @@
             }
             for (IMediaRouter2Manager manager : managers) {
                 try {
-                    manager.notifyPreferredFeaturesChanged(routerPackageName, preferredFeatures);
+                    manager.notifyDiscoveryPreferenceChanged(routerPackageName,
+                            discoveryPreference);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to notify preferred features changed."
                             + " Manager probably died.", ex);
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 583cdd5..647a89e 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -104,6 +104,12 @@
             return -1 * Boolean.compare(leftPeople, rightPeople);
         }
 
+        boolean leftSystemMax = isSystemMax(left);
+        boolean rightSystemMax = isSystemMax(right);
+        if (leftSystemMax != rightSystemMax) {
+            return -1 * Boolean.compare(leftSystemMax, rightSystemMax);
+        }
+
         if (leftImportance != rightImportance) {
             // by importance, high to low
             return -1 * Integer.compare(leftImportance, rightImportance);
@@ -173,6 +179,20 @@
         return mMessagingUtil.isImportantMessaging(record.getSbn(), record.getImportance());
     }
 
+    protected boolean isSystemMax(NotificationRecord record) {
+        if (record.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
+            return false;
+        }
+        String packageName = record.getSbn().getPackageName();
+        if ("android".equals(packageName)) {
+            return true;
+        }
+        if ("com.android.systemui".equals(packageName)) {
+            return true;
+        }
+        return false;
+    }
+
     private boolean isOngoing(NotificationRecord record) {
         final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE;
         return (record.getNotification().flags & ongoingFlags) != 0;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 96d7521..70e968f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -201,6 +201,10 @@
     private boolean mIsAppImportanceLocked;
     private ArraySet<Uri> mGrantableUris;
 
+    // Storage for phone numbers that were found to be associated with
+    // contacts in this notification.
+    private ArraySet<String> mPhoneNumbers;
+
     // Whether this notification record should have an update logged the next time notifications
     // are sorted.
     private boolean mPendingLogUpdate = false;
@@ -1547,6 +1551,26 @@
         return mPendingLogUpdate;
     }
 
+    /**
+     * Merge the given set of phone numbers into the list of phone numbers that
+     * are cached on this notification record.
+     */
+    public void mergePhoneNumbers(ArraySet<String> phoneNumbers) {
+        // if the given phone numbers are null or empty then don't do anything
+        if (phoneNumbers == null || phoneNumbers.size() == 0) {
+            return;
+        }
+        // initialize if not already
+        if (mPhoneNumbers == null) {
+            mPhoneNumbers = new ArraySet<>();
+        }
+        mPhoneNumbers.addAll(phoneNumbers);
+    }
+
+    public ArraySet<String> getPhoneNumbers() {
+        return mPhoneNumbers;
+    }
+
     @VisibleForTesting
     static final class Light {
         public final int color;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 0cbdbc1..5d18069 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -19,7 +19,7 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.permission.PermissionManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -77,7 +77,8 @@
         assertFlag();
         final long callingId = Binder.clearCallingIdentity();
         try {
-            return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+            return mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(uid)
+                    == PERMISSION_GRANTED;
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index d7bc3bb..dc4d04f 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -68,7 +68,10 @@
     private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
     private static final String SETTING_ENABLE_PEOPLE_VALIDATOR =
             "validate_notification_people_enabled";
-    private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.STARRED };
+    private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.LOOKUP_KEY,
+            Contacts.STARRED, Contacts.HAS_PHONE_NUMBER };
+    private static final String[] PHONE_LOOKUP_PROJECTION =
+            { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER };
     private static final int MAX_PEOPLE = 10;
     private static final int PEOPLE_CACHE_SIZE = 200;
 
@@ -409,6 +412,35 @@
         return lookupResult;
     }
 
+    @VisibleForTesting
+    // Performs a contacts search using searchContacts, and then follows up by looking up
+    // any phone numbers associated with the resulting contact information and merge those
+    // into the lookup result as well. Will have no additional effect if the contact does
+    // not have any phone numbers.
+    LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
+        LookupResult lookupResult = searchContacts(context, lookupUri);
+        String phoneLookupKey = lookupResult.getPhoneLookupKey();
+        if (phoneLookupKey != null) {
+            String selection = Contacts.LOOKUP_KEY + " = ?";
+            String[] selectionArgs = new String[] { phoneLookupKey };
+            try (Cursor cursor = context.getContentResolver().query(
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
+                    selection, selectionArgs, /* sortOrder= */ null)) {
+                if (cursor == null) {
+                    Slog.w(TAG, "Cursor is null when querying contact phone number.");
+                    return lookupResult;
+                }
+
+                while (cursor.moveToNext()) {
+                    lookupResult.mergePhoneNumber(cursor);
+                }
+            } catch (Throwable t) {
+                Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
+            }
+        }
+        return lookupResult;
+    }
+
     private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) {
         final int workUserId = findWorkUserId(context);
         if (workUserId == -1) {
@@ -454,6 +486,9 @@
 
         private final long mExpireMillis;
         private float mAffinity = NONE;
+        private boolean mHasPhone = false;
+        private String mPhoneLookupKey = null;
+        private ArraySet<String> mPhoneNumbers = new ArraySet<>();
 
         public LookupResult() {
             mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
@@ -473,6 +508,15 @@
                 Slog.i(TAG, "invalid cursor: no _ID");
             }
 
+            // Lookup key for potentially looking up contact phone number later
+            final int lookupKeyIdx = cursor.getColumnIndex(Contacts.LOOKUP_KEY);
+            if (lookupKeyIdx >= 0) {
+                mPhoneLookupKey = cursor.getString(lookupKeyIdx);
+                if (DEBUG) Slog.d(TAG, "contact LOOKUP_KEY is: " + mPhoneLookupKey);
+            } else {
+                if (DEBUG) Slog.d(TAG, "invalid cursor: no LOOKUP_KEY");
+            }
+
             // Starred
             final int starIdx = cursor.getColumnIndex(Contacts.STARRED);
             if (starIdx >= 0) {
@@ -484,6 +528,39 @@
             } else {
                 if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED");
             }
+
+            // whether a phone number is present
+            final int hasPhoneIdx = cursor.getColumnIndex(Contacts.HAS_PHONE_NUMBER);
+            if (hasPhoneIdx >= 0) {
+                mHasPhone = cursor.getInt(hasPhoneIdx) != 0;
+                if (DEBUG) Slog.d(TAG, "contact HAS_PHONE_NUMBER is: " + mHasPhone);
+            } else {
+                if (DEBUG) Slog.d(TAG, "invalid cursor: no HAS_PHONE_NUMBER");
+            }
+        }
+
+        // Returns the phone lookup key that is cached in this result, or null
+        // if the contact has no known phone info.
+        public String getPhoneLookupKey() {
+            if (!mHasPhone) {
+                return null;
+            }
+            return mPhoneLookupKey;
+        }
+
+        // Merge phone number found in this lookup and store it in mPhoneNumbers.
+        public void mergePhoneNumber(Cursor cursor) {
+            final int phoneNumIdx = cursor.getColumnIndex(
+                    ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER);
+            if (phoneNumIdx >= 0) {
+                mPhoneNumbers.add(cursor.getString(phoneNumIdx));
+            } else {
+                if (DEBUG) Slog.d(TAG, "invalid cursor: no NORMALIZED_NUMBER");
+            }
+        }
+
+        public ArraySet<String> getPhoneNumbers() {
+            return mPhoneNumbers;
         }
 
         private boolean isExpired() {
@@ -509,6 +586,7 @@
         // Amount of time to wait for a result from the contacts db before rechecking affinity.
         private static final long LOOKUP_TIME = 1000;
         private float mContactAffinity = NONE;
+        private ArraySet<String> mPhoneNumbers = null;
         private NotificationRecord mRecord;
 
         private PeopleRankingReconsideration(Context context, String key,
@@ -543,7 +621,9 @@
                         lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
                     } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
                         if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
-                        lookupResult = searchContacts(mContext, uri);
+                        // only look up phone number if this is a contact lookup uri and thus isn't
+                        // already directly a phone number.
+                        lookupResult = searchContactsAndLookupNumbers(mContext, uri);
                     } else {
                         lookupResult = new LookupResult();  // invalid person for the cache
                         if (!"name".equals(uri.getScheme())) {
@@ -561,6 +641,13 @@
                         Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
                     }
                     mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
+                    // merge any phone numbers found in this lookup result
+                    if (lookupResult.getPhoneNumbers() != null) {
+                        if (mPhoneNumbers == null) {
+                            mPhoneNumbers = new ArraySet<>();
+                        }
+                        mPhoneNumbers.addAll(lookupResult.getPhoneNumbers());
+                    }
                 } else {
                     if (DEBUG) Slog.d(TAG, "lookupResult is null");
                 }
@@ -581,6 +668,7 @@
             float affinityBound = operand.getContactAffinity();
             operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
             if (VERBOSE) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
+            operand.mergePhoneNumbers(mPhoneNumbers);
         }
 
         public float getContactAffinity() {
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index b186f61..d04b331 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -24,18 +24,24 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.media.AudioAttributes;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.service.notification.ZenModeConfig;
 import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.util.NotificationMessagingUtil;
 
 import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
 import java.util.Date;
 
 public class ZenModeFiltering {
@@ -64,13 +70,22 @@
         pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
         pw.println(REPEAT_CALLERS.mThresholdMinutes);
         synchronized (REPEAT_CALLERS) {
-            if (!REPEAT_CALLERS.mCalls.isEmpty()) {
-                pw.print(prefix); pw.println("RepeatCallers.mCalls=");
-                for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
+            if (!REPEAT_CALLERS.mTelCalls.isEmpty()) {
+                pw.print(prefix); pw.println("RepeatCallers.mTelCalls=");
+                for (int i = 0; i < REPEAT_CALLERS.mTelCalls.size(); i++) {
                     pw.print(prefix); pw.print("  ");
-                    pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
+                    pw.print(REPEAT_CALLERS.mTelCalls.keyAt(i));
                     pw.print(" at ");
-                    pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
+                    pw.println(ts(REPEAT_CALLERS.mTelCalls.valueAt(i)));
+                }
+            }
+            if (!REPEAT_CALLERS.mOtherCalls.isEmpty()) {
+                pw.print(prefix); pw.println("RepeatCallers.mOtherCalls=");
+                for (int i = 0; i < REPEAT_CALLERS.mOtherCalls.size(); i++) {
+                    pw.print(prefix); pw.print("  ");
+                    pw.print(REPEAT_CALLERS.mOtherCalls.keyAt(i));
+                    pw.print(" at ");
+                    pw.println(ts(REPEAT_CALLERS.mOtherCalls.valueAt(i)));
                 }
             }
         }
@@ -126,7 +141,7 @@
     }
 
     protected void recordCall(NotificationRecord record) {
-        REPEAT_CALLERS.recordCall(mContext, extras(record));
+        REPEAT_CALLERS.recordCall(mContext, extras(record), record.getPhoneNumbers());
     }
 
     /**
@@ -330,34 +345,40 @@
     }
 
     private static class RepeatCallers {
-        // Person : time
-        private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
+        // We keep a separate map per uri scheme to do more generous number-matching
+        // handling on telephone numbers specifically. For other inputs, we
+        // simply match directly on the string.
+        private final ArrayMap<String, Long> mTelCalls = new ArrayMap<>();
+        private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>();
         private int mThresholdMinutes;
 
-        private synchronized void recordCall(Context context, Bundle extras) {
+        private synchronized void recordCall(Context context, Bundle extras,
+                ArraySet<String> phoneNumbers) {
             setThresholdMinutes(context);
             if (mThresholdMinutes <= 0 || extras == null) return;
-            final String peopleString = peopleString(extras);
-            if (peopleString == null) return;
+            final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+            if (extraPeople == null || extraPeople.length == 0) return;
             final long now = System.currentTimeMillis();
-            cleanUp(mCalls, now);
-            mCalls.put(peopleString, now);
+            cleanUp(mTelCalls, now);
+            cleanUp(mOtherCalls, now);
+            recordCallers(extraPeople, phoneNumbers, now);
         }
 
         private synchronized boolean isRepeat(Context context, Bundle extras) {
             setThresholdMinutes(context);
             if (mThresholdMinutes <= 0 || extras == null) return false;
-            final String peopleString = peopleString(extras);
-            if (peopleString == null) return false;
+            final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+            if (extraPeople == null || extraPeople.length == 0) return false;
             final long now = System.currentTimeMillis();
-            cleanUp(mCalls, now);
-            return mCalls.containsKey(peopleString);
+            cleanUp(mTelCalls, now);
+            cleanUp(mOtherCalls, now);
+            return checkCallers(context, extraPeople);
         }
 
         private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) {
             final int N = calls.size();
             for (int i = N - 1; i >= 0; i--) {
-                final long time = mCalls.valueAt(i);
+                final long time = calls.valueAt(i);
                 if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
                     calls.removeAt(i);
                 }
@@ -367,10 +388,16 @@
         // Clean up all calls that occurred after the given time.
         // Used only for tests, to clean up after testing.
         private synchronized void cleanUpCallsAfter(long timeThreshold) {
-            for (int i = mCalls.size() - 1; i >= 0; i--) {
-                final long time = mCalls.valueAt(i);
+            for (int i = mTelCalls.size() - 1; i >= 0; i--) {
+                final long time = mTelCalls.valueAt(i);
                 if (time > timeThreshold) {
-                    mCalls.removeAt(i);
+                    mTelCalls.removeAt(i);
+                }
+            }
+            for (int j = mOtherCalls.size() - 1; j >= 0; j--) {
+                final long time = mOtherCalls.valueAt(j);
+                if (time > timeThreshold) {
+                    mOtherCalls.removeAt(j);
                 }
             }
         }
@@ -382,21 +409,74 @@
             }
         }
 
-        private static String peopleString(Bundle extras) {
-            final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
-            if (extraPeople == null || extraPeople.length == 0) return null;
-            final StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < extraPeople.length; i++) {
-                String extraPerson = extraPeople[i];
-                if (extraPerson == null) continue;
-                extraPerson = extraPerson.trim();
-                if (extraPerson.isEmpty()) continue;
-                if (sb.length() > 0) {
-                    sb.append('|');
+        private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers,
+                long now) {
+            for (int i = 0; i < people.length; i++) {
+                String person = people[i];
+                if (person == null) continue;
+                final Uri uri = Uri.parse(person);
+                if ("tel".equals(uri.getScheme())) {
+                    String tel = uri.getSchemeSpecificPart();
+                    // while ideally we should not need to do this, sometimes we have seen tel
+                    // numbers given in a url-encoded format
+                    try {
+                        tel = URLDecoder.decode(tel, "UTF-8");
+                    } catch (UnsupportedEncodingException e) {
+                        // ignore, keep the original tel string
+                        Slog.w(TAG, "unsupported encoding in tel: uri input");
+                    }
+                    mTelCalls.put(tel, now);
+                } else {
+                    // for non-tel calls, store the entire string, uri-component and all
+                    mOtherCalls.put(person, now);
                 }
-                sb.append(extraPerson);
             }
-            return sb.length() == 0 ? null : sb.toString();
+
+            // record any additional numbers from the notification record if
+            // provided; these are in the format of just a phone number string
+            if (phoneNumbers != null) {
+                for (String num : phoneNumbers) {
+                    mTelCalls.put(num, now);
+                }
+            }
+        }
+
+        private synchronized boolean checkCallers(Context context, String[] people) {
+            // get the default country code for checking telephone numbers
+            final String defaultCountryCode =
+                    context.getSystemService(TelephonyManager.class).getNetworkCountryIso();
+            for (int i = 0; i < people.length; i++) {
+                String person = people[i];
+                if (person == null) continue;
+                final Uri uri = Uri.parse(person);
+                if ("tel".equals(uri.getScheme())) {
+                    String number = uri.getSchemeSpecificPart();
+                    if (mTelCalls.containsKey(number)) {
+                        // check directly via map first
+                        return true;
+                    } else {
+                        // see if a number that matches via areSameNumber exists
+                        String numberToCheck = number;
+                        try {
+                            numberToCheck = URLDecoder.decode(number, "UTF-8");
+                        } catch (UnsupportedEncodingException e) {
+                            // ignore, continue to use the original string
+                            Slog.w(TAG, "unsupported encoding in tel: uri input");
+                        }
+                        for (String prev : mTelCalls.keySet()) {
+                            if (PhoneNumberUtils.areSamePhoneNumber(
+                                    numberToCheck, prev, defaultCountryCode)) {
+                                return true;
+                            }
+                        }
+                    }
+                } else {
+                    if (mOtherCalls.containsKey(person)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 2e9ad50..2d87099 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -32,9 +32,6 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Binder;
@@ -59,6 +56,9 @@
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.component.ParsedApexSystemService;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.TimingsTraceAndSlog;
 
 import com.google.android.collect.Lists;
@@ -414,9 +414,11 @@
             throws PackageManagerException;
 
     /**
-     * Get a map of system services defined in an apex mapped to the jar files they reside in.
+     * Get a list of apex system services implemented in an apex.
+     *
+     * <p>The list is sorted by initOrder for consistency.
      */
-    public abstract Map<String, String> getApexSystemServices();
+    public abstract List<ApexSystemServiceInfo> getApexSystemServices();
 
     /**
      * Dumps various state information to the provided {@link PrintWriter} object.
@@ -449,7 +451,7 @@
          * Map of all apex system services to the jar files they are contained in.
          */
         @GuardedBy("mLock")
-        private Map<String, String> mApexSystemServices = new ArrayMap<>();
+        private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
 
         /**
          * Contains the list of {@code packageName}s of apks-in-apex for given
@@ -605,14 +607,19 @@
                         }
 
                         String name = service.getName();
-                        if (mApexSystemServices.containsKey(name)) {
-                            throw new IllegalStateException(String.format(
-                                    "Duplicate apex-system-service %s from %s, %s",
-                                    name, mApexSystemServices.get(name), service.getJarPath()));
+                        for (ApexSystemServiceInfo info : mApexSystemServices) {
+                            if (info.getName().equals(name)) {
+                                throw new IllegalStateException(String.format(
+                                        "Duplicate apex-system-service %s from %s, %s",
+                                        name, info.mJarPath, service.getJarPath()));
+                            }
                         }
 
-                        mApexSystemServices.put(name, service.getJarPath());
+                        ApexSystemServiceInfo info = new ApexSystemServiceInfo(
+                                service.getName(), service.getJarPath(), service.getInitOrder());
+                        mApexSystemServices.add(info);
                     }
+                    Collections.sort(mApexSystemServices);
                     mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
                     if (ai.isActive) {
                         if (activePackagesSet.contains(packageInfo.packageName)) {
@@ -1133,7 +1140,7 @@
         }
 
         @Override
-        public Map<String, String> getApexSystemServices() {
+        public List<ApexSystemServiceInfo> getApexSystemServices() {
             synchronized (mLock) {
                 Preconditions.checkState(mApexSystemServices != null,
                         "APEX packages have not been scanned");
@@ -1423,10 +1430,10 @@
         }
 
         @Override
-        public Map<String, String> getApexSystemServices() {
+        public List<ApexSystemServiceInfo> getApexSystemServices() {
             // TODO(satayev): we can't really support flattened apex use case, and need to migrate
             // the manifest entries into system's manifest asap.
-            return Collections.emptyMap();
+            return Collections.emptyList();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
new file mode 100644
index 0000000..f75ba6d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
@@ -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.server.pm;
+
+import android.annotation.Nullable;
+
+/**
+ * A helper class that contains information about apex-system-service to be used within system
+ * server process.
+ */
+public final class ApexSystemServiceInfo implements Comparable<ApexSystemServiceInfo> {
+
+    final String mName;
+    @Nullable
+    final String mJarPath;
+    final int mInitOrder;
+
+    public ApexSystemServiceInfo(String name, String jarPath, int initOrder) {
+        this.mName = name;
+        this.mJarPath = jarPath;
+        this.mInitOrder = initOrder;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getJarPath() {
+        return mJarPath;
+    }
+
+    public int getInitOrder() {
+        return mInitOrder;
+    }
+
+    @Override
+    public int compareTo(ApexSystemServiceInfo other) {
+        if (mInitOrder == other.mInitOrder) {
+            return mName.compareTo(other.mName);
+        }
+        // higher initOrder values take precedence
+        return -Integer.compare(mInitOrder, other.mInitOrder);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 50c26f4..336da2a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -141,7 +141,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -2358,26 +2358,26 @@
                 if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
                     // Check for updated system application.
                     if (installedPkg.isSystem()) {
-                        return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                        return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
                     } else {
                         // If current upgrade specifies particular preference
                         if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
                             // Application explicitly specified internal.
-                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                            return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
                         } else if (
                                 installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
                             // App explicitly prefers external. Let policy decide
                         } else {
                             // Prefer previous location
                             if (installedPkg.isExternalStorage()) {
-                                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                                return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
                             }
-                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                            return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
                         }
                     }
                 } else {
                     // Invalid install. Return error code
-                    return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
+                    return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS;
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index d996fe4..7e845c7 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -35,18 +35,17 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.PackageLite;
-import android.os.Environment;
 import android.os.Message;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.storage.StorageManager;
 import android.util.ArrayMap;
 import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -142,12 +141,8 @@
      * Only {@link PackageManager#INSTALL_INTERNAL} flag may mutate in
      * {@link #mInstallFlags}
      */
-    private int overrideInstallLocation(PackageInfoLite pkgLite) {
-        final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
-        if (DEBUG_INSTANT && ephemeral) {
-            Slog.v(TAG, "pkgLite for install: " + pkgLite);
-        }
-
+    private int overrideInstallLocation(String packageName, int recommendedInstallLocation,
+            int installLocation) {
         if (mOriginInfo.mStaged) {
             // If we're already staged, we've firmly committed to an install location
             if (mOriginInfo.mFile != null) {
@@ -155,77 +150,35 @@
             } else {
                 throw new IllegalStateException("Invalid stage location");
             }
-        } else if (pkgLite.recommendedInstallLocation
-                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
-            /*
-             * If we are not staged and have too little free space, try to free cache
-             * before giving up.
-             */
-            // TODO: focus freeing disk space on the target device
-            final StorageManager storage = StorageManager.from(mPm.mContext);
-            final long lowThreshold = storage.getStorageLowBytes(
-                    Environment.getDataDirectory());
-
-            final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
-                    mOriginInfo.mResolvedPath, mPackageAbiOverride);
-            if (sizeBytes >= 0) {
-                synchronized (mPm.mInstallLock) {
-                    try {
-                        mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
-                        pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
-                                mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags,
-                                mPackageAbiOverride);
-                    } catch (Installer.InstallerException e) {
-                        Slog.w(TAG, "Failed to free cache", e);
-                    }
-                }
-            }
-
-            /*
-             * The cache free must have deleted the file we downloaded to install.
-             *
-             * TODO: fix the "freeCache" call to not delete the file we care about.
-             */
-            if (pkgLite.recommendedInstallLocation
-                    == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
-                pkgLite.recommendedInstallLocation =
-                        PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+        }
+        if (recommendedInstallLocation < 0) {
+            return InstallLocationUtils.getInstallationErrorCode(recommendedInstallLocation);
+        }
+        // Override with defaults if needed.
+        synchronized (mPm.mLock) {
+            // reader
+            AndroidPackage installedPkg = mPm.mPackages.get(packageName);
+            if (installedPkg != null) {
+                // Currently installed package which the new package is attempting to replace
+                recommendedInstallLocation = InstallLocationUtils.installLocationPolicy(
+                        installLocation, recommendedInstallLocation, mInstallFlags,
+                        installedPkg.isSystem(), installedPkg.isExternalStorage());
             }
         }
 
-        int ret = INSTALL_SUCCEEDED;
-        int loc = pkgLite.recommendedInstallLocation;
-        if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
-            ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
-        } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
-            ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
-        } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
-            ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
-        } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
-            ret = PackageManager.INSTALL_FAILED_INVALID_APK;
-        } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
-            ret = PackageManager.INSTALL_FAILED_INVALID_URI;
-        } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
-            ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
-        } else {
-            // Override with defaults if needed.
-            loc = mInstallPackageHelper.installLocationPolicy(pkgLite, mInstallFlags);
+        final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
 
-            final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
-
-            if (!onInt) {
-                // Override install location with flags
-                if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
-                    // Set the flag to install on external media.
-                    mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
-                } else {
-                    // Make sure the flag for installing on external
-                    // media is unset
-                    mInstallFlags |= PackageManager.INSTALL_INTERNAL;
-                }
+        if (!onInt) {
+            // Override install location with flags
+            if (recommendedInstallLocation == InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL) {
+                // Set the flag to install on external media.
+                mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
+            } else {
+                // Make sure the flag for installing on external media is unset
+                mInstallFlags |= PackageManager.INSTALL_INTERNAL;
             }
         }
-        return ret;
+        return INSTALL_SUCCEEDED;
     }
 
     /*
@@ -254,7 +207,21 @@
             }
         }
 
-        mRet = overrideInstallLocation(pkgLite);
+        final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+        if (DEBUG_INSTANT && ephemeral) {
+            Slog.v(TAG, "pkgLite for install: " + pkgLite);
+        }
+
+        if (!mOriginInfo.mStaged && pkgLite.recommendedInstallLocation
+                == InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+            // If we are not staged and have too little free space, try to free cache
+            // before giving up.
+            pkgLite.recommendedInstallLocation = mPm.freeCacheForInstallation(
+                    pkgLite.recommendedInstallLocation, mPackageLite,
+                    mOriginInfo.mResolvedPath, mPackageAbiOverride, mInstallFlags);
+        }
+        mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation,
+                pkgLite.installLocation);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ccc375f..8465248 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -84,7 +84,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ImageUtils;
@@ -782,7 +782,7 @@
             // If caller requested explicit location, validity check it, otherwise
             // resolve the best internal or adopted location.
             if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
-                if (!PackageHelper.fitsOnInternal(mContext, params)) {
+                if (!InstallLocationUtils.fitsOnInternal(mContext, params)) {
                     throw new IOException("No suitable internal storage available");
                 }
             } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
@@ -796,7 +796,7 @@
                 // requested install flags, delta size, and manifest settings.
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
+                    params.volumeUuid = InstallLocationUtils.resolveInstallVolume(mContext, params);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 390dd3f..7152783 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -143,8 +143,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.security.VerityUtils;
@@ -1537,7 +1537,7 @@
             if (stageDir != null && lengthBytes > 0) {
                 mContext.getSystemService(StorageManager.class).allocateBytes(
                         targetPfd.getFileDescriptor(), lengthBytes,
-                        PackageHelper.translateAllocateFlags(params.installFlags));
+                        InstallLocationUtils.translateAllocateFlags(params.installFlags));
             }
 
             if (offsetBytes > 0) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 79cfa06..ee5c638 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -101,6 +101,7 @@
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageChangeEvent;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ComponentEnabledSetting;
@@ -129,6 +130,7 @@
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.IArtManager;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.PackageLite;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -189,7 +191,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.telephony.CarrierAppUtils;
 import com.android.internal.util.ArrayUtils;
@@ -245,6 +247,7 @@
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
 import com.android.server.storage.DeviceStorageMonitorInternal;
+import com.android.server.supplementalprocess.SupplementalProcessManagerLocal;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.utils.Watchable;
@@ -932,6 +935,7 @@
     final @Nullable String mOverlayConfigSignaturePackage;
     final @Nullable String mRecentsPackage;
     final @Nullable String mAmbientContextDetectionPackage;
+    private final @NonNull String mRequiredSupplementalProcessPackage;
 
     @GuardedBy("mLock")
     private final PackageUsage mPackageUsage = new PackageUsage();
@@ -1669,6 +1673,7 @@
         mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
         mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
         mResolveComponentName = testParams.resolveComponentName;
+        mRequiredSupplementalProcessPackage = testParams.requiredSupplementalProcessPackage;
 
         mLiveComputer = createLiveComputer();
         mSnapshotComputer = null;
@@ -1693,6 +1698,7 @@
         mResolveIntentHelper = testParams.resolveIntentHelper;
         mDexOptHelper = testParams.dexOptHelper;
         mSuspendPackageHelper = testParams.suspendPackageHelper;
+
         mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
 
         registerObservers(false);
@@ -2136,6 +2142,9 @@
                     getPackageInfo(mRequiredPermissionControllerPackage, 0,
                             UserHandle.USER_SYSTEM).getLongVersionCode());
 
+            // Resolve the supplemental process
+            mRequiredSupplementalProcessPackage = getRequiredSupplementalProcessPackageName();
+
             // Initialize InstantAppRegistry's Instant App list for all users.
             for (AndroidPackage pkg : mPackages.values()) {
                 if (pkg.isSystem()) {
@@ -2901,6 +2910,36 @@
         throw new IOException("Failed to free " + bytes + " on storage device at " + file);
     }
 
+    int freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite,
+            String resolvedPath, String mPackageAbiOverride, int installFlags) {
+        // TODO: focus freeing disk space on the target device
+        final StorageManager storage = StorageManager.from(mContext);
+        final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory());
+
+        final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(resolvedPath,
+                mPackageAbiOverride);
+        if (sizeBytes >= 0) {
+            synchronized (mInstallLock) {
+                try {
+                    mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
+                    PackageInfoLite pkgInfoLite = PackageManagerServiceUtils.getMinimalPackageInfo(
+                            mContext, pkgLite, resolvedPath, installFlags,
+                            mPackageAbiOverride);
+                    // The cache free must have deleted the file we downloaded to install.
+                    if (pkgInfoLite.recommendedInstallLocation
+                            == InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI) {
+                        pkgInfoLite.recommendedInstallLocation =
+                                InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+                    }
+                    return pkgInfoLite.recommendedInstallLocation;
+                } catch (Installer.InstallerException e) {
+                    Slog.w(TAG, "Failed to free cache", e);
+                }
+            }
+        }
+        return recommendedInstallLocation;
+    }
+
     /**
      * Update given flags when being used to request {@link PackageInfo}.
      */
@@ -3121,6 +3160,11 @@
         throw new IllegalStateException("PermissionController is not found");
     }
 
+    @Override
+    public String getSupplementalProcessPackageName() {
+        return mRequiredSupplementalProcessPackage;
+    }
+
     String getPackageInstallerPackageName() {
         return mRequiredInstallerPackage;
     }
@@ -3676,7 +3720,7 @@
 
         // Before everything else, see whether we need to fstrim.
         try {
-            IStorageManager sm = PackageHelper.getStorageManager();
+            IStorageManager sm = InstallLocationUtils.getStorageManager();
             if (sm != null) {
                 boolean doTrim = false;
                 final long interval = android.provider.Settings.Global.getLong(
@@ -5486,6 +5530,24 @@
         }
     }
 
+    private @NonNull String getRequiredSupplementalProcessPackageName() {
+        final Intent intent = new Intent(SupplementalProcessManagerLocal.SERVICE_INTERFACE);
+
+        final List<ResolveInfo> matches = queryIntentServicesInternal(
+                intent,
+                /* resolvedType= */ null,
+                MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                UserHandle.USER_SYSTEM,
+                /* callingUid= */ Process.myUid(),
+                /* includeInstantApps= */ false);
+        if (matches.size() == 1) {
+            return matches.get(0).getComponentInfo().packageName;
+        } else {
+            throw new RuntimeException("There should exactly one supplemental process; found "
+                    + matches.size() + ": matches=" + matches);
+        }
+    }
+
     @Override
     public String getDefaultTextClassifierPackageName() {
         return ensureSystemPackageName(
@@ -6595,8 +6657,9 @@
         if (getInstallLocation() == loc) {
             return true;
         }
-        if (loc == PackageHelper.APP_INSTALL_AUTO || loc == PackageHelper.APP_INSTALL_INTERNAL
-                || loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+        if (loc == InstallLocationUtils.APP_INSTALL_AUTO
+                || loc == InstallLocationUtils.APP_INSTALL_INTERNAL
+                || loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
             android.provider.Settings.Global.putInt(mContext.getContentResolver(),
                     android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, loc);
             return true;
@@ -6609,7 +6672,7 @@
         // allow instant app access
         return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                 android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION,
-                PackageHelper.APP_INSTALL_AUTO);
+                InstallLocationUtils.APP_INSTALL_AUTO);
     }
 
     /** Called by UserManagerService */
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 1caa76d..d12c826 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -89,6 +89,7 @@
     public @Nullable String defaultTextClassifierPackage;
     public @Nullable String systemTextClassifierPackage;
     public @Nullable String overlayConfigSignaturePackage;
+    public @NonNull String requiredSupplementalProcessPackage;
     public ViewCompiler viewCompiler;
     public @Nullable String retailDemoPackage;
     public @Nullable String recentsPackage;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 19c31e0..d6340b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -43,6 +43,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageInfoLite;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackagePartitions;
 import android.content.pm.ResolveInfo;
@@ -79,8 +80,8 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -805,27 +806,37 @@
         final PackageInfoLite ret = new PackageInfoLite();
         if (packagePath == null || pkg == null) {
             Slog.i(TAG, "Invalid package file " + packagePath);
-            ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+            ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
             return ret;
         }
 
         final File packageFile = new File(packagePath);
         final long sizeBytes;
         try {
-            sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
+            sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
         } catch (IOException e) {
             if (!packageFile.exists()) {
-                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
+                ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI;
             } else {
-                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+                ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
             }
 
             return ret;
         }
 
-        final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
-                pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags);
-
+        final PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_INVALID);
+        sessionParams.appPackageName = pkg.getPackageName();
+        sessionParams.installLocation = pkg.getInstallLocation();
+        sessionParams.sizeBytes = sizeBytes;
+        sessionParams.installFlags = flags;
+        final int recommendedInstallLocation;
+        try {
+            recommendedInstallLocation = InstallLocationUtils.resolveInstallLocation(context,
+                    sessionParams);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
         ret.packageName = pkg.getPackageName();
         ret.splitNames = pkg.getSplitNames();
         ret.versionCode = pkg.getVersionCode();
@@ -837,7 +848,6 @@
         ret.recommendedInstallLocation = recommendedInstallLocation;
         ret.multiArch = pkg.isMultiArch();
         ret.debuggable = pkg.isDebuggable();
-
         return ret;
     }
 
@@ -857,7 +867,7 @@
                 throw new PackageManagerException(result.getErrorCode(),
                         result.getErrorMessage(), result.getException());
             }
-            return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride);
+            return InstallLocationUtils.calculateInstalledSize(result.getResult(), abiOverride);
         } catch (PackageManagerException | IOException e) {
             Slog.w(TAG, "Failed to calculate installed size: " + e);
             return -1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 99f70b2..d4fcd06 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -98,7 +98,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -593,7 +593,7 @@
                         null /* splitApkPaths */, null /* splitRevisionCodes */,
                         apkLite.getTargetSdkVersion(), null /* requiredSplitTypes */,
                         null /* splitTypes */);
-                sessionSize += PackageHelper.calculateInstalledSize(pkgLite,
+                sessionSize += InstallLocationUtils.calculateInstalledSize(pkgLite,
                         params.sessionParams.abiOverride, fd.getFileDescriptor());
             } catch (IOException e) {
                 getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
@@ -922,7 +922,7 @@
                 final List<SharedLibraryInfo> libs = libsSlice.getList();
                 for (int l = 0, lsize = libs.size(); l < lsize; ++l) {
                     SharedLibraryInfo lib = libs.get(l);
-                    if (lib.getType() == SharedLibraryInfo.TYPE_SDK) {
+                    if (lib.getType() == SharedLibraryInfo.TYPE_SDK_PACKAGE) {
                         name = lib.getName() + ":" + lib.getLongVersion();
                         break;
                     }
@@ -1649,11 +1649,11 @@
     private int runGetInstallLocation() throws RemoteException {
         int loc = mInterface.getInstallLocation();
         String locStr = "invalid";
-        if (loc == PackageHelper.APP_INSTALL_AUTO) {
+        if (loc == InstallLocationUtils.APP_INSTALL_AUTO) {
             locStr = "auto";
-        } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
+        } else if (loc == InstallLocationUtils.APP_INSTALL_INTERNAL) {
             locStr = "internal";
-        } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+        } else if (loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
             locStr = "external";
         }
         getOutPrintWriter().println(loc + "[" + locStr + "]");
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 9bfb7d1..9db215e 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -28,7 +28,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.rollback.RollbackInfo;
@@ -43,11 +42,12 @@
 import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
 import com.android.server.rollback.RollbackManagerInternal;
 
 import java.io.File;
@@ -291,8 +291,8 @@
             throws PackageManagerException {
         // Before marking the session as ready, start checkpoint service if available
         try {
-            if (PackageHelper.getStorageManager().supportsCheckpoint()) {
-                PackageHelper.getStorageManager().startCheckpoint(2);
+            if (InstallLocationUtils.getStorageManager().supportsCheckpoint()) {
+                InstallLocationUtils.getStorageManager().startCheckpoint(2);
             }
         } catch (Exception e) {
             // Failed to get hold of StorageManager
@@ -544,7 +544,7 @@
      */
     private void checkActiveSessions() throws PackageManagerException {
         try {
-            checkActiveSessions(PackageHelper.getStorageManager().supportsCheckpoint());
+            checkActiveSessions(InstallLocationUtils.getStorageManager().supportsCheckpoint());
         } catch (RemoteException e) {
             throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
                     "Can't query fs-checkpoint status : " + e);
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 29de555..f63f8f4 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
@@ -237,7 +237,7 @@
                     mApexManager.revertActiveSessions();
                 }
 
-                PackageHelper.getStorageManager().abortChanges(
+                InstallLocationUtils.getStorageManager().abortChanges(
                         "abort-staged-install", false /*retry*/);
             }
         } catch (Exception e) {
@@ -674,8 +674,8 @@
         boolean needsCheckpoint = false;
         boolean supportsCheckpoint = false;
         try {
-            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
-            needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint();
+            supportsCheckpoint = InstallLocationUtils.getStorageManager().supportsCheckpoint();
+            needsCheckpoint = InstallLocationUtils.getStorageManager().needsCheckpoint();
         } catch (RemoteException e) {
             // This means that vold has crashed, and device is in a bad state.
             throw new IllegalStateException("Failed to get checkpoint status", e);
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 1fa9013..9bcb724 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -285,6 +285,19 @@
     );
 
     /**
+     * User restrictions available to a device owner whose type is
+     * {@link android.app.admin.DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+     */
+    private static final Set<String> FINANCED_DEVICE_OWNER_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_ADD_USER,
+            UserManager.DISALLOW_DEBUGGING_FEATURES,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_SAFE_BOOT,
+            UserManager.DISALLOW_CONFIG_DATE_TIME,
+            UserManager.DISALLOW_OUTGOING_CALLS
+    );
+
+    /**
      * Returns whether the given restriction name is valid (and logs it if it isn't).
      */
     public static boolean isValidRestriction(@NonNull String restriction) {
@@ -458,6 +471,15 @@
     }
 
     /**
+     * @return {@code true} only if the restriction is allowed for financed devices and can be set
+     * by a device owner. Otherwise, {@code false} would be returned.
+     */
+    public static boolean canFinancedDeviceOwnerChange(String restriction) {
+        return FINANCED_DEVICE_OWNER_RESTRICTIONS.contains(restriction)
+                && canDeviceOwnerChange(restriction);
+    }
+
+    /**
      * Whether given user restriction should be enforced globally.
      */
     public static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType,
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 7e59bd6..f2b1a71 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
@@ -92,7 +92,7 @@
                 AndroidPackageUtils.getAllCodePaths(pkg),
                 pkg.getSdkLibName(),
                 pkg.getSdkLibVersionMajor(),
-                SharedLibraryInfo.TYPE_SDK,
+                SharedLibraryInfo.TYPE_SDK_PACKAGE,
                 new VersionedPackage(pkg.getManifestPackageName(),
                         pkg.getLongVersionCode()),
                 null, null, false /* isNative */);
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 317730a..edc0e3d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.permission;
 
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
 import static android.Manifest.permission.RECORD_AUDIO;
 import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
@@ -50,6 +51,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
@@ -608,6 +610,22 @@
         }
 
         @Override
+        public int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid) {
+            int granted = PermissionManagerService.this.checkUidPermission(uid,
+                    POST_NOTIFICATIONS);
+            AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
+            if (granted != PackageManager.PERMISSION_GRANTED
+                    && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) {
+                int flags = PermissionManagerService.this.getPermissionFlags(pkg.getPackageName(),
+                        POST_NOTIFICATIONS, UserHandle.getUserId(uid));
+                if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+            return granted;
+        }
+
+        @Override
         public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName,
                 @Nullable List<String> permissionNames) {
             Objects.requireNonNull(packageName, "packageName");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index d2c4ec4..812d7a0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -63,6 +63,17 @@
     int checkUidPermission(int uid, @NonNull String permissionName);
 
     /**
+     * Check whether a particular UID has been granted the POST_NOTIFICATIONS permission, or if
+     * access should be granted based on legacy access (currently symbolized by the REVIEW_REQUIRED
+     * permission flag
+     *
+     * @param uid the UID
+     * @return {@code PERMISSION_GRANTED} if the permission is granted, or legacy access is granted,
+     *         {@code PERMISSION_DENIED} otherwise
+     */
+    int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid);
+
+    /**
      * Adds a listener for runtime permission state (permissions or flags) changes.
      *
      * @param listener The listener.
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
index 586d2c4..cf478b1 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
@@ -34,4 +34,7 @@
 
     @Nullable
     String getMaxSdkVersion();
+
+    int getInitOrder();
+
 }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
index 1e427d0..167aba3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
@@ -48,18 +48,18 @@
     @Nullable
     private String maxSdkVersion;
 
+    private int initOrder;
+
     public ParsedApexSystemServiceImpl() {
     }
 
-
-
     // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -71,13 +71,15 @@
             @NonNull String name,
             @Nullable String jarPath,
             @Nullable String minSdkVersion,
-            @Nullable String maxSdkVersion) {
+            @Nullable String maxSdkVersion,
+            int initOrder) {
         this.name = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
         this.jarPath = jarPath;
         this.minSdkVersion = minSdkVersion;
         this.maxSdkVersion = maxSdkVersion;
+        this.initOrder = initOrder;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -103,6 +105,11 @@
     }
 
     @DataClass.Generated.Member
+    public int getInitOrder() {
+        return initOrder;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) {
         name = value;
         com.android.internal.util.AnnotationValidations.validate(
@@ -129,6 +136,12 @@
     }
 
     @DataClass.Generated.Member
+    public @NonNull ParsedApexSystemServiceImpl setInitOrder( int value) {
+        initOrder = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
     static Parcelling<String> sParcellingForName =
             Parcelling.Cache.get(
                     Parcelling.BuiltIn.ForInternedString.class);
@@ -187,6 +200,7 @@
         sParcellingForJarPath.parcel(jarPath, dest, flags);
         sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags);
         sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags);
+        dest.writeInt(initOrder);
     }
 
     @Override
@@ -205,6 +219,7 @@
         String _jarPath = sParcellingForJarPath.unparcel(in);
         String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in);
         String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in);
+        int _initOrder = in.readInt();
 
         this.name = _name;
         com.android.internal.util.AnnotationValidations.validate(
@@ -212,6 +227,7 @@
         this.jarPath = _jarPath;
         this.minSdkVersion = _minSdkVersion;
         this.maxSdkVersion = _maxSdkVersion;
+        this.initOrder = _initOrder;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -231,10 +247,10 @@
     };
 
     @DataClass.Generated(
-            time = 1641431950080L,
+            time = 1643723578605L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
-            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java",
+            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate  int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.server.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
index 38a6f5a35..ed9aa2e 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
@@ -53,10 +53,13 @@
                     R.styleable.AndroidManifestApexSystemService_minSdkVersion);
             String maxSdkVersion = sa.getString(
                     R.styleable.AndroidManifestApexSystemService_maxSdkVersion);
+            int initOrder = sa.getInt(R.styleable.AndroidManifestApexSystemService_initOrder, 0);
 
             systemService.setName(className)
                     .setMinSdkVersion(minSdkVersion)
-                    .setMaxSdkVersion(maxSdkVersion);
+                    .setMaxSdkVersion(maxSdkVersion)
+                    .setInitOrder(initOrder);
+
             if (!TextUtils.isEmpty(jarPath)) {
                 systemService.setJarPath(jarPath);
             }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 73ec2cd..77d6310 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -33,6 +33,7 @@
 import android.net.Uri;
 import android.os.BatteryStats;
 import android.os.Handler;
+import android.os.IWakeLockCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
@@ -215,14 +216,15 @@
      * Called when a wake lock is acquired.
      */
     public void onWakeLockAcquired(int flags, String tag, String packageName,
-            int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
+            int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+            IWakeLockCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
                     + "\", packageName=" + packageName
                     + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                     + ", workSource=" + workSource);
         }
-
+        notifyWakeLockListener(callback, true);
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         if (monitorType >= 0) {
             try {
@@ -300,8 +302,9 @@
      */
     public void onWakeLockChanging(int flags, String tag, String packageName,
             int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
-            int newFlags, String newTag, String newPackageName, int newOwnerUid,
-            int newOwnerPid, WorkSource newWorkSource, String newHistoryTag) {
+            IWakeLockCallback callback, int newFlags, String newTag, String newPackageName,
+            int newOwnerUid, int newOwnerPid, WorkSource newWorkSource, String newHistoryTag,
+            IWakeLockCallback newCallback) {
 
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags);
@@ -323,10 +326,16 @@
             } catch (RemoteException ex) {
                 // Ignore
             }
-        } else {
-            onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag);
+        } else if (!PowerManagerService.isSameCallback(callback, newCallback)) {
+            onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag,
+                    null /* Do not notify the old callback */);
             onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
-                    newWorkSource, newHistoryTag);
+                    newWorkSource, newHistoryTag, newCallback /* notify the new callback */);
+        } else {
+            onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag,
+                    callback);
+            onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
+                    newWorkSource, newHistoryTag, newCallback);
         }
     }
 
@@ -334,14 +343,15 @@
      * Called when a wake lock is released.
      */
     public void onWakeLockReleased(int flags, String tag, String packageName,
-            int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
+            int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+            IWakeLockCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
                     + "\", packageName=" + packageName
                     + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                     + ", workSource=" + workSource);
         }
-
+        notifyWakeLockListener(callback, false);
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         if (monitorType >= 0) {
             try {
@@ -859,6 +869,18 @@
         return enabled && dndOff;
     }
 
+    private void notifyWakeLockListener(IWakeLockCallback callback, boolean isEnabled) {
+        if (callback != null) {
+            mHandler.post(() -> {
+                try {
+                    callback.onStateChanged(isEnabled);
+                } catch (RemoteException e) {
+                    throw new IllegalArgumentException("Wakelock.mCallback is already dead.", e);
+                }
+            });
+        }
+    }
+
     private final class NotifierHandler extends Handler {
 
         public NotifierHandler(Looper looper) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index efcfbdd..3857072 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -66,6 +66,7 @@
 import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IPowerManager;
+import android.os.IWakeLockCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelDuration;
@@ -1436,7 +1437,8 @@
     }
 
     private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag,
-            String packageName, WorkSource ws, String historyTag, int uid, int pid) {
+            String packageName, WorkSource ws, String historyTag, int uid, int pid,
+            @Nullable IWakeLockCallback callback) {
         synchronized (mLock) {
             if (displayId != Display.INVALID_DISPLAY) {
                 final DisplayInfo displayInfo =
@@ -1460,11 +1462,12 @@
             boolean notifyAcquire;
             if (index >= 0) {
                 wakeLock = mWakeLocks.get(index);
-                if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
+                if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid, callback)) {
                     // Update existing wake lock.  This shouldn't happen but is harmless.
                     notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
-                            uid, pid, ws, historyTag);
-                    wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);
+                            uid, pid, ws, historyTag, callback);
+                    wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid,
+                            callback);
                 }
                 notifyAcquire = false;
             } else {
@@ -1476,12 +1479,7 @@
                 }
                 state.mNumWakeLocks++;
                 wakeLock = new WakeLock(lock, displayId, flags, tag, packageName, ws, historyTag,
-                        uid, pid, state);
-                try {
-                    lock.linkToDeath(wakeLock, 0);
-                } catch (RemoteException ex) {
-                    throw new IllegalArgumentException("Wake lock is already dead.");
-                }
+                        uid, pid, state, callback);
                 mWakeLocks.add(wakeLock);
                 setWakeLockDisabledStateLocked(wakeLock);
                 notifyAcquire = true;
@@ -1576,11 +1574,8 @@
                 mRequestWaitForNegativeProximity = true;
             }
 
-            try {
-                wakeLock.mLock.unlinkToDeath(wakeLock, 0);
-            } catch (NoSuchElementException e) {
-                Slog.wtf(TAG, "Failed to unlink wakelock", e);
-            }
+            wakeLock.unlinkToDeath();
+            wakeLock.setDisabled(true);
             removeWakeLockLocked(wakeLock, index);
         }
     }
@@ -1650,13 +1645,41 @@
             if (!wakeLock.hasSameWorkSource(ws)) {
                 notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
                         wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
-                        ws, historyTag);
+                        ws, historyTag, null);
                 wakeLock.mHistoryTag = historyTag;
                 wakeLock.updateWorkSource(ws);
             }
         }
     }
 
+    private void updateWakeLockCallbackInternal(IBinder lock, IWakeLockCallback callback,
+            int callingUid) {
+        synchronized (mLock) {
+            int index = findWakeLockIndexLocked(lock);
+            if (index < 0) {
+                if (DEBUG_SPEW) {
+                    Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock)
+                            + " [not found]");
+                }
+                throw new IllegalArgumentException("Wake lock not active: " + lock
+                        + " from uid " + callingUid);
+            }
+
+            WakeLock wakeLock = mWakeLocks.get(index);
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock)
+                        + " [" + wakeLock.mTag + "]");
+            }
+
+            if (!isSameCallback(callback, wakeLock.mCallback)) {
+                notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
+                        wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
+                        wakeLock.mWorkSource, wakeLock.mHistoryTag, callback);
+                wakeLock.mCallback = callback;
+            }
+        }
+    }
+
     @GuardedBy("mLock")
     private int findWakeLockIndexLocked(IBinder lock) {
         final int count = mWakeLocks.size();
@@ -1684,7 +1707,7 @@
             wakeLock.mNotifiedAcquired = true;
             mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
                     wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
-                    wakeLock.mHistoryTag);
+                    wakeLock.mHistoryTag, wakeLock.mCallback);
             restartNofifyLongTimerLocked(wakeLock);
         }
     }
@@ -1726,11 +1749,13 @@
 
     @GuardedBy("mLock")
     private void notifyWakeLockChangingLocked(WakeLock wakeLock, int flags, String tag,
-            String packageName, int uid, int pid, WorkSource ws, String historyTag) {
+            String packageName, int uid, int pid, WorkSource ws, String historyTag,
+            IWakeLockCallback callback) {
         if (mSystemReady && wakeLock.mNotifiedAcquired) {
             mNotifier.onWakeLockChanging(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
                     wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
-                    wakeLock.mHistoryTag, flags, tag, packageName, uid, pid, ws, historyTag);
+                    wakeLock.mHistoryTag, wakeLock.mCallback, flags, tag, packageName, uid, pid, ws,
+                    historyTag, callback);
             notifyWakeLockLongFinishedLocked(wakeLock);
             // Changing the wake lock will count as releasing the old wake lock(s) and
             // acquiring the new ones...  we do this because otherwise once a wakelock
@@ -1747,7 +1772,7 @@
             wakeLock.mAcquireTime = 0;
             mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag,
                     wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
-                    wakeLock.mWorkSource, wakeLock.mHistoryTag);
+                    wakeLock.mWorkSource, wakeLock.mHistoryTag, wakeLock.mCallback);
             notifyWakeLockLongFinishedLocked(wakeLock);
         }
     }
@@ -4045,10 +4070,7 @@
                     }
                 }
             }
-            if (wakeLock.mDisabled != disabled) {
-                wakeLock.mDisabled = disabled;
-                return true;
-            }
+            return wakeLock.setDisabled(disabled);
         }
         return false;
     }
@@ -5041,10 +5063,11 @@
         public boolean mNotifiedAcquired;
         public boolean mNotifiedLong;
         public boolean mDisabled;
+        public IWakeLockCallback mCallback;
 
         public WakeLock(IBinder lock, int displayId, int flags, String tag, String packageName,
                 WorkSource workSource, String historyTag, int ownerUid, int ownerPid,
-                UidState uidState) {
+                UidState uidState, @Nullable IWakeLockCallback callback) {
             mLock = lock;
             mDisplayId = displayId;
             mFlags = flags;
@@ -5055,15 +5078,43 @@
             mOwnerUid = ownerUid;
             mOwnerPid = ownerPid;
             mUidState = uidState;
+            mCallback = callback;
+            linkToDeath();
         }
 
         @Override
         public void binderDied() {
+            unlinkToDeath();
             PowerManagerService.this.handleWakeLockDeath(this);
         }
 
+        private void linkToDeath() {
+            try {
+                mLock.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                throw new IllegalArgumentException("Wakelock.mLock is already dead.");
+            }
+        }
+
+        @GuardedBy("mLock")
+        void unlinkToDeath() {
+            try {
+                mLock.unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                Slog.wtf(TAG, "Failed to unlink Wakelock.mLock", e);
+            }
+        }
+
+        public boolean setDisabled(boolean disabled) {
+            if (mDisabled != disabled) {
+                mDisabled = disabled;
+                return true;
+            } else {
+                return false;
+            }
+        }
         public boolean hasSameProperties(int flags, String tag, WorkSource workSource,
-                int ownerUid, int ownerPid) {
+                int ownerUid, int ownerPid, IWakeLockCallback callback) {
             return mFlags == flags
                     && mTag.equals(tag)
                     && hasSameWorkSource(workSource)
@@ -5072,7 +5123,8 @@
         }
 
         public void updateProperties(int flags, String tag, String packageName,
-                WorkSource workSource, String historyTag, int ownerUid, int ownerPid) {
+                WorkSource workSource, String historyTag, int ownerUid, int ownerPid,
+                IWakeLockCallback callback) {
             if (!mPackageName.equals(packageName)) {
                 throw new IllegalStateException("Existing wake lock package name changed: "
                         + mPackageName + " to " + packageName);
@@ -5089,6 +5141,7 @@
             mTag = tag;
             updateWorkSource(workSource);
             mHistoryTag = historyTag;
+            mCallback = callback;
         }
 
         public boolean hasSameWorkSource(WorkSource workSource) {
@@ -5307,11 +5360,12 @@
 
         @Override // Binder call
         public void acquireWakeLockWithUid(IBinder lock, int flags, String tag,
-                String packageName, int uid, int displayId) {
+                String packageName, int uid, int displayId, IWakeLockCallback callback) {
             if (uid < 0) {
                 uid = Binder.getCallingUid();
             }
-            acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null, displayId);
+            acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null,
+                    displayId, callback);
         }
 
         @Override // Binder call
@@ -5346,7 +5400,8 @@
 
         @Override // Binder call
         public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
-                WorkSource ws, String historyTag, int displayId) {
+                WorkSource ws, String historyTag, int displayId,
+                @Nullable IWakeLockCallback callback) {
             if (lock == null) {
                 throw new IllegalArgumentException("lock must not be null");
             }
@@ -5386,7 +5441,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag,
-                        uid, pid);
+                        uid, pid, callback);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -5395,7 +5450,8 @@
         @Override // Binder call
         public void acquireWakeLockAsync(IBinder lock, int flags, String tag, String packageName,
                 WorkSource ws, String historyTag) {
-            acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY);
+            acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY,
+                    null);
         }
 
         @Override // Binder call
@@ -5463,6 +5519,23 @@
         }
 
         @Override // Binder call
+        public void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback) {
+            if (lock == null) {
+                throw new IllegalArgumentException("lock must not be null");
+            }
+
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+
+            final int callingUid = Binder.getCallingUid();
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                updateWakeLockCallbackInternal(lock, callback, callingUid);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public boolean isWakeLockLevelSupported(int level) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -6510,4 +6583,15 @@
         }
     };
 
+    static boolean isSameCallback(IWakeLockCallback callback1,
+            IWakeLockCallback callback2) {
+        if (callback1 == callback2) {
+            return true;
+        }
+        if (callback1 != null && callback2 != null
+                && callback1.asBinder() == callback2.asBinder()) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java
new file mode 100644
index 0000000..cc27546
--- /dev/null
+++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.resources;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.IResourcesManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import com.android.server.SystemService;
+import com.android.server.am.ActivityManagerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A service for managing information about ResourcesManagers
+ */
+public class ResourcesManagerService extends SystemService {
+    private ActivityManagerService mActivityManagerService;
+
+    /**
+     * Initializes the system service.
+     * <p>
+     * Subclasses must define a single argument constructor that accepts the context
+     * and passes it to super.
+     * </p>
+     *
+     * @param context The system server context.
+     */
+    public ResourcesManagerService(@NonNull Context context) {
+        super(context);
+        publishBinderService(Context.RESOURCES_SERVICE, mService);
+    }
+
+    @Override
+    public void onStart() {
+        // Intentionally left empty.
+    }
+
+    private final IBinder mService = new IResourcesManager.Stub() {
+        @Override
+        public boolean dumpResources(String process, ParcelFileDescriptor fd,
+                RemoteCallback callback) throws RemoteException {
+            final int callingUid = Binder.getCallingUid();
+            if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
+                callback.sendResult(null);
+                throw new SecurityException("dump should only be called by shell");
+            }
+            return mActivityManagerService.dumpResources(process, fd, callback);
+        }
+
+        @Override
+        protected void dump(@NonNull FileDescriptor fd,
+                @NonNull PrintWriter pw, @Nullable String[] args) {
+            try {
+                mActivityManagerService.dumpAllResources(ParcelFileDescriptor.dup(fd), pw);
+            } catch (Exception e) {
+                pw.println("Exception while trying to dump all resources: " + e.getMessage());
+                e.printStackTrace(pw);
+            }
+        }
+
+        @Override
+        public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+                @NonNull ParcelFileDescriptor out,
+                @NonNull ParcelFileDescriptor err,
+                @NonNull String[] args) {
+            return (new ResourcesManagerShellCommand(this)).exec(
+                    this,
+                    in.getFileDescriptor(),
+                    out.getFileDescriptor(),
+                    err.getFileDescriptor(),
+                    args);
+        }
+    };
+
+    public void setActivityManagerService(
+            ActivityManagerService activityManagerService) {
+        mActivityManagerService = activityManagerService;
+    }
+}
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
new file mode 100644
index 0000000..7d8336a
--- /dev/null
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.resources;
+
+import android.content.res.IResourcesManager;
+import android.os.ConditionVariable;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Shell command handler for resources related commands
+ */
+public class ResourcesManagerShellCommand extends ShellCommand {
+    private static final String TAG = "ResourcesManagerShellCommand";
+
+    private final IResourcesManager mInterface;
+
+    public ResourcesManagerShellCommand(IResourcesManager anInterface) {
+        mInterface = anInterface;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter err = getErrPrintWriter();
+        try {
+            switch (cmd) {
+                case "dump":
+                    return dumpResources();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (IllegalArgumentException e) {
+            err.println("Error: " + e.getMessage());
+        } catch (RemoteException e) {
+            err.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private int dumpResources() throws RemoteException {
+        String processId = getNextArgRequired();
+        try {
+            ConditionVariable lock = new ConditionVariable();
+            RemoteCallback
+                    finishCallback = new RemoteCallback(result -> lock.open(), null);
+
+            if (!mInterface.dumpResources(processId,
+                    ParcelFileDescriptor.dup(getOutFileDescriptor()), finishCallback)) {
+                getErrPrintWriter().println("RESOURCES DUMP FAILED on process " + processId);
+                return -1;
+            }
+            lock.block(5000);
+            return 0;
+        } catch (IOException e) {
+            Slog.e(TAG, "Exception while dumping resources", e);
+            getErrPrintWriter().println("Exception while dumping resources: " + e.getMessage());
+        }
+        return -1;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter out = getOutPrintWriter();
+        out.println("Resources manager commands:");
+        out.println("  help");
+        out.println("    Print this help text.");
+        out.println("  dump <PROCESS>");
+        out.println("    Dump the Resources objects in use as well as the history of Resources");
+
+    }
+}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index f519ced..243efb5 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.security;
 
+import static android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED;
+import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
 import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
 
 import android.content.Context;
@@ -76,10 +78,24 @@
     private void verifyAttestationForAllVerifiers(
             AttestationProfile profile, int localBindingType, Bundle requirements,
             byte[] attestation, AndroidFuture<IVerificationResult> resultCallback) {
-        // TODO(b/201696614): Implement
         IVerificationResult result = new IVerificationResult();
-        result.resultCode = RESULT_UNKNOWN;
+        // TODO(b/201696614): Implement
         result.token = null;
+        switch (profile.getAttestationProfileId()) {
+            case PROFILE_SELF_TRUSTED:
+                Slog.d(TAG, "Verifying Self trusted profile.");
+                try {
+                    result.resultCode =
+                            AttestationVerificationSelfTrustedVerifierForTesting.getInstance()
+                                    .verifyAttestation(localBindingType, requirements, attestation);
+                } catch (Throwable t) {
+                    result.resultCode = RESULT_FAILURE;
+                }
+                break;
+            default:
+                Slog.d(TAG, "No profile found, defaulting.");
+                result.resultCode = RESULT_UNKNOWN;
+        }
         resultCallback.complete(result);
     }
 
diff --git a/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java
new file mode 100644
index 0000000..58df2bd
--- /dev/null
+++ b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.security;
+
+import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
+import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
+import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
+import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
+
+import android.annotation.NonNull;
+import android.os.Build;
+import android.os.Bundle;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import com.android.internal.org.bouncycastle.asn1.ASN1OctetString;
+import com.android.internal.org.bouncycastle.asn1.ASN1Sequence;
+import com.android.internal.org.bouncycastle.asn1.x509.Certificate;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.PKIXParameters;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Verifies {@code PROFILE_SELF_TRUSTED} attestations.
+ *
+ * Verifies that the attesting environment can create an attestation with the same root certificate
+ * as the verifying device with a matching attestation challenge. Skips CRL revocations checking
+ * so this verifier can work in a hermetic test environment.
+ *
+ * This verifier profile is intended to be used only for testing.
+ */
+class AttestationVerificationSelfTrustedVerifierForTesting {
+    private static final String TAG = "AVF";
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
+
+    // The OID for the extension Android Keymint puts into device-generated certificates.
+    private static final String ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID =
+            "1.3.6.1.4.1.11129.2.1.17";
+
+    // ASN.1 sequence index values for the Android Keymint extension.
+    private static final int ATTESTATION_CHALLENGE_INDEX = 4;
+
+    private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
+    private static final String GOLDEN_ALIAS =
+            AttestationVerificationSelfTrustedVerifierForTesting.class.getCanonicalName()
+                    + ".Golden";
+
+    private static volatile AttestationVerificationSelfTrustedVerifierForTesting
+            sAttestationVerificationSelfTrustedVerifier = null;
+
+    private final CertificateFactory mCertificateFactory;
+    private final CertPathValidator mCertPathValidator;
+    private final KeyStore mAndroidKeyStore;
+    private X509Certificate mGoldenRootCert;
+
+    static AttestationVerificationSelfTrustedVerifierForTesting getInstance()
+            throws Exception {
+        if (sAttestationVerificationSelfTrustedVerifier == null) {
+            synchronized (AttestationVerificationSelfTrustedVerifierForTesting.class) {
+                if (sAttestationVerificationSelfTrustedVerifier == null) {
+                    sAttestationVerificationSelfTrustedVerifier =
+                            new AttestationVerificationSelfTrustedVerifierForTesting();
+                }
+            }
+        }
+        return sAttestationVerificationSelfTrustedVerifier;
+    }
+
+    private static void debugVerboseLog(String str, Throwable t) {
+        if (DEBUG) {
+            Slog.v(TAG, str, t);
+        }
+    }
+
+    private static void debugVerboseLog(String str) {
+        if (DEBUG) {
+            Slog.v(TAG, str);
+        }
+    }
+
+    private AttestationVerificationSelfTrustedVerifierForTesting() throws Exception {
+        mCertificateFactory = CertificateFactory.getInstance("X.509");
+        mCertPathValidator = CertPathValidator.getInstance("PKIX");
+        mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
+        mAndroidKeyStore.load(null);
+        if (!mAndroidKeyStore.containsAlias(GOLDEN_ALIAS)) {
+            KeyPairGenerator kpg =
+                    KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE);
+            KeyGenParameterSpec parameterSpec = new KeyGenParameterSpec.Builder(
+                    GOLDEN_ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setAttestationChallenge(GOLDEN_ALIAS.getBytes())
+                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512).build();
+            kpg.initialize(parameterSpec);
+            kpg.generateKeyPair();
+        }
+
+        X509Certificate[] goldenCerts = (X509Certificate[])
+                ((KeyStore.PrivateKeyEntry) mAndroidKeyStore.getEntry(GOLDEN_ALIAS, null))
+                        .getCertificateChain();
+        mGoldenRootCert = goldenCerts[goldenCerts.length - 1];
+    }
+
+    int verifyAttestation(
+            int localBindingType, @NonNull Bundle requirements,  @NonNull byte[] attestation) {
+        List<X509Certificate> certificates = new ArrayList<>();
+        ByteArrayInputStream bis = new ByteArrayInputStream(attestation);
+        try {
+            while (bis.available() > 0) {
+                certificates.add((X509Certificate) mCertificateFactory.generateCertificate(bis));
+            }
+        } catch (CertificateException e) {
+            debugVerboseLog("Unable to parse certificates from attestation", e);
+            return RESULT_FAILURE;
+        }
+
+        if (localBindingType == TYPE_CHALLENGE
+                && validateRequirements(requirements)
+                && checkLeafChallenge(requirements, certificates)
+                && verifyCertificateChain(certificates)) {
+            return RESULT_SUCCESS;
+        }
+
+        return RESULT_FAILURE;
+    }
+
+    private boolean verifyCertificateChain(List<X509Certificate> certificates) {
+        if (certificates.size() < 2) {
+            debugVerboseLog("Certificate chain less than 2 in size.");
+            return false;
+        }
+
+        try {
+            CertPath certificatePath = mCertificateFactory.generateCertPath(certificates);
+            PKIXParameters validationParams = new PKIXParameters(getTrustAnchors());
+            // Skipping revocation checking because we want this to work in a hermetic test
+            // environment.
+            validationParams.setRevocationEnabled(false);
+            mCertPathValidator.validate(certificatePath, validationParams);
+        } catch (Throwable t) {
+            debugVerboseLog("Invalid certificate chain", t);
+            return false;
+        }
+
+        return true;
+    }
+
+    private Set<TrustAnchor> getTrustAnchors() {
+        return Collections.singleton(new TrustAnchor(mGoldenRootCert, null));
+    }
+
+    private boolean validateRequirements(Bundle requirements) {
+        if (requirements.size() != 1) {
+            debugVerboseLog("Requirements does not contain exactly 1 key.");
+            return false;
+        }
+        if (!requirements.containsKey(PARAM_CHALLENGE)) {
+            debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean checkLeafChallenge(Bundle requirements, List<X509Certificate> certificates) {
+        // Verify challenge
+        byte[] challenge;
+        try {
+            challenge = getChallengeFromCert(certificates.get(0));
+        } catch (Throwable t) {
+            debugVerboseLog("Unable to parse challenge from certificate.", t);
+            return false;
+        }
+
+        if (Arrays.equals(requirements.getByteArray(PARAM_CHALLENGE), challenge)) {
+            return true;
+        } else {
+            debugVerboseLog("Self-Trusted validation failed; challenge mismatch.");
+            return false;
+        }
+    }
+
+    private byte[] getChallengeFromCert(@NonNull X509Certificate x509Certificate)
+            throws CertificateEncodingException, IOException {
+        Certificate certificate = Certificate.getInstance(
+                new ASN1InputStream(x509Certificate.getEncoded()).readObject());
+        ASN1Sequence keyAttributes = (ASN1Sequence) certificate.getTBSCertificate().getExtensions()
+                .getExtensionParsedValue(
+                        new ASN1ObjectIdentifier(ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID));
+        return ((ASN1OctetString) keyAttributes.getObjectAt(ATTESTATION_CHALLENGE_INDEX))
+                .getOctets();
+    }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e71ff78..94f483c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -47,11 +47,13 @@
 import android.graphics.drawable.Icon;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.media.MediaRoute2Info;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -91,6 +93,7 @@
 import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.IUndoMediaTransferCallback;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.internal.statusbar.StatusBarIcon;
@@ -157,6 +160,8 @@
     private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
     @GuardedBy("mLock")
     private IUdfpsHbmListener mUdfpsHbmListener;
+    @GuardedBy("mLock")
+    private IBiometricContextListener mBiometricContextListener;
 
     @GuardedBy("mCurrentRequestAddTilePackages")
     private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>();
@@ -894,6 +899,20 @@
     }
 
     @Override
+    public void setBiometicContextListener(IBiometricContextListener listener) {
+        enforceStatusBarService();
+        synchronized (mLock) {
+            mBiometricContextListener = listener;
+        }
+        if (mBar != null) {
+            try {
+                mBar.setBiometicContextListener(listener);
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    @Override
     public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
         enforceStatusBarService();
         if (mBar != null) {
@@ -1248,6 +1267,12 @@
                 "StatusBarManagerService");
     }
 
+    private void enforceMediaContentControl() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MEDIA_CONTENT_CONTROL,
+                "StatusBarManagerService");
+    }
+
     /**
      *  For targetSdk S+ we require STATUS_BAR. For targetSdk < S, we only require EXPAND_STATUS_BAR
      *  but also require that it falls into one of the allowed use-cases to lock down abuse vector.
@@ -1315,6 +1340,7 @@
         mHandler.post(() -> {
             synchronized (mLock) {
                 setUdfpsHbmListener(mUdfpsHbmListener);
+                setBiometicContextListener(mBiometricContextListener);
             }
         });
     }
@@ -1969,6 +1995,53 @@
         return false;
     }
 
+    /**
+     * Notifies the system of a new media tap-to-transfer state for the *sender* device. See
+     * {@link StatusBarManager.updateMediaTapToTransferSenderDisplay} for more information.
+     *
+     * @param undoCallback a callback that will be triggered if the user elects to undo a media
+     *                     transfer.
+     *
+     * Requires the caller to have the {@link android.Manifest.permission.MEDIA_CONTENT_CONTROL}
+     * permission.
+     */
+    @Override
+    public void updateMediaTapToTransferSenderDisplay(
+            @StatusBarManager.MediaTransferSenderState int displayState,
+            @NonNull MediaRoute2Info routeInfo,
+            @Nullable IUndoMediaTransferCallback undoCallback
+    ) {
+        enforceMediaContentControl();
+        if (mBar != null) {
+            try {
+                mBar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "updateMediaTapToTransferSenderDisplay", e);
+            }
+        }
+    }
+
+    /**
+     * Notifies the system of a new media tap-to-transfer state for the *receiver* device. See
+     * {@link StatusBarManager.updateMediaTapToTransferReceiverDisplay} for more information.
+     *
+     * Requires the caller to have the {@link android.Manifest.permission.MEDIA_CONTENT_CONTROL}
+     * permission.
+     */
+    @Override
+    public void updateMediaTapToTransferReceiverDisplay(
+            @StatusBarManager.MediaTransferReceiverState int displayState,
+            MediaRoute2Info routeInfo) {
+        enforceMediaContentControl();
+        if (mBar != null) {
+            try {
+                mBar.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e);
+            }
+        }
+    }
+
     /** @hide */
     public void passThroughShellCommand(String[] args, FileDescriptor fd) {
         enforceStatusBarOrShell();
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 06ce4a4..1dea3d7 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -71,6 +71,7 @@
     private static final int MSG_ESCROW_TOKEN_STATE = 9;
     private static final int MSG_UNLOCK_USER = 10;
     private static final int MSG_SHOW_KEYGUARD_ERROR_MESSAGE = 11;
+    private static final int MSG_LOCK_USER = 12;
 
     /**
      * Time in uptime millis that we wait for the service connection, both when starting
@@ -100,6 +101,8 @@
 
     // Trust state
     private boolean mTrusted;
+    private boolean mWaitingForTrustableDowngrade = false;
+    private boolean mTrustable;
     private CharSequence mMessage;
     private boolean mDisplayTrustGrantedMessage;
     private boolean mTrustDisabledByDpm;
@@ -108,6 +111,25 @@
     private AlarmManager mAlarmManager;
     private final Intent mAlarmIntent;
     private PendingIntent mAlarmPendingIntent;
+    private final BroadcastReceiver mTrustableDowngradeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) {
+                return;
+            }
+            if (!mWaitingForTrustableDowngrade) {
+                return;
+            }
+            // are these the broadcasts we want to listen to
+            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())
+                    || Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
+                mTrusted = false;
+                mTrustable = true;
+                mWaitingForTrustableDowngrade = false;
+                mTrustManagerService.updateTrust(mUserId, 0);
+            }
+        }
+    };
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -126,16 +148,21 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_GRANT_TRUST:
-                    // TODO(b/213631675): Respect FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
                     if (!isConnected()) {
                         Log.w(TAG, "Agent is not connected, cannot grant trust: "
                                 + mName.flattenToShortString());
                         return;
                     }
                     mTrusted = true;
+                    mTrustable = false;
                     mMessage = (CharSequence) msg.obj;
                     int flags = msg.arg1;
                     mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
+                    if ((flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
+                        mWaitingForTrustableDowngrade = true;
+                    } else {
+                        mWaitingForTrustableDowngrade = false;
+                    }
                     long durationMs = msg.getData().getLong(DATA_DURATION);
                     if (durationMs > 0) {
                         final long duration;
@@ -270,6 +297,13 @@
                     mTrustManagerService.showKeyguardErrorMessage(message);
                     break;
                 }
+                case MSG_LOCK_USER: {
+                    mTrusted = false;
+                    mTrustable = false;
+                    mTrustManagerService.updateTrust(mUserId, 0 /* flags */);
+                    mTrustManagerService.lockUser(mUserId);
+                    break;
+                }
             }
         }
     };
@@ -295,6 +329,11 @@
         }
 
         @Override
+        public void lockUser() {
+            mHandler.sendEmptyMessage(MSG_LOCK_USER);
+        }
+
+        @Override
         public void setManagingTrust(boolean managingTrust) {
             if (DEBUG) Slog.d(TAG, "managingTrust()");
             mHandler.obtainMessage(MSG_MANAGING_TRUST, managingTrust ? 1 : 0, 0).sendToTarget();
@@ -427,6 +466,9 @@
         final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME);
         alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL);
 
+        IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+        trustableFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
         // Schedules a restart for when connecting times out. If the connection succeeds,
         // the restart is canceled in mCallback's onConnected.
         scheduleRestart();
@@ -435,6 +477,7 @@
         if (mBound) {
             mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null,
                     Context.RECEIVER_EXPORTED);
+            mContext.registerReceiver(mTrustableDowngradeReceiver, trustableFilter);
         } else {
             Log.e(TAG, "Can't bind to TrustAgent " + mName.flattenToShortString());
         }
@@ -591,6 +634,10 @@
         return mTrusted && mManagingTrust && !mTrustDisabledByDpm;
     }
 
+    public boolean isTrustable() {
+        return mTrustable && mManagingTrust && !mTrustDisabledByDpm;
+    }
+
     public boolean isManagingTrust() {
         return mManagingTrust && !mTrustDisabledByDpm;
     }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9bed24d..6aafd4a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -54,6 +54,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -65,6 +66,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.Xml;
 import android.view.IWindowManager;
@@ -145,6 +147,21 @@
     @GuardedBy("mUserIsTrusted")
     private final SparseBooleanArray mUserIsTrusted = new SparseBooleanArray();
 
+    //TODO(b/215724686): remove flag
+    public static final boolean ENABLE_ACTIVE_UNLOCK_FLAG = SystemProperties.getBoolean(
+            "fw.enable_active_unlock_flag", true);
+
+    private enum TrustState {
+        UNTRUSTED, // the phone is not unlocked by any trustagents
+        TRUSTABLE, // the phone is in a semi-locked state that can be unlocked if
+        // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE is passed and a trustagent is trusted
+        TRUSTED // the phone is unlocked
+    };
+
+    @GuardedBy("mUserTrustState")
+    private final SparseArray<TrustManagerService.TrustState> mUserTrustState =
+            new SparseArray<>();
+
     /**
      * Stores the locked state for users on the device. There are three different type of users
      * which are handled slightly differently:
@@ -228,7 +245,6 @@
     }
 
     // Extend unlock config and logic
-
     private final class SettingsObserver extends ContentObserver {
         private final Uri TRUST_AGENTS_EXTEND_UNLOCK =
                 Settings.Secure.getUriFor(Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK);
@@ -396,6 +412,14 @@
     }
 
     private void updateTrust(int userId, int flags, boolean isFromUnlock) {
+        if (ENABLE_ACTIVE_UNLOCK_FLAG) {
+            updateTrustWithRenewableUnlock(userId, flags, isFromUnlock);
+        } else {
+            updateTrustWithExtendUnlock(userId, flags, isFromUnlock);
+        }
+    }
+
+    private void updateTrustWithExtendUnlock(int userId, int flags, boolean isFromUnlock) {
         boolean managed = aggregateIsTrustManaged(userId);
         dispatchOnTrustManagedChanged(managed, userId);
         if (mStrongAuthTracker.isTrustAllowedForUser(userId)
@@ -441,6 +465,65 @@
         }
     }
 
+    private void updateTrustWithRenewableUnlock(int userId, int flags, boolean isFromUnlock) {
+        boolean managed = aggregateIsTrustManaged(userId);
+        dispatchOnTrustManagedChanged(managed, userId);
+        if (mStrongAuthTracker.isTrustAllowedForUser(userId)
+                && isTrustUsuallyManagedInternal(userId) != managed) {
+            updateTrustUsuallyManaged(userId, managed);
+        }
+
+        boolean trustedByAtLeastOneAgent = aggregateIsTrusted(userId);
+        boolean trustableByAtLeastOneAgent = aggregateIsTrustable(userId);
+        boolean wasTrusted;
+        boolean wasTrustable;
+        TrustState pendingTrustState;
+
+        IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+        boolean alreadyUnlocked = false;
+        try {
+            alreadyUnlocked = !wm.isKeyguardLocked();
+        } catch (RemoteException e) {
+        }
+
+        synchronized (mUserTrustState) {
+            wasTrusted = (mUserTrustState.get(userId) == TrustState.TRUSTED);
+            wasTrustable = (mUserTrustState.get(userId) == TrustState.TRUSTABLE);
+            boolean renewingTrust = wasTrustable && (
+                    (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
+            boolean canMoveToTrusted = alreadyUnlocked || isFromUnlock || renewingTrust;
+            boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
+
+            if (trustedByAtLeastOneAgent && wasTrusted) {
+                // no change
+                return;
+            } else if (trustedByAtLeastOneAgent && canMoveToTrusted
+                    && upgradingTrustForCurrentUser) {
+                pendingTrustState = TrustState.TRUSTED;
+            } else if (trustableByAtLeastOneAgent && (wasTrusted || wasTrustable)
+                    && upgradingTrustForCurrentUser) {
+                pendingTrustState = TrustState.TRUSTABLE;
+            } else {
+                pendingTrustState = TrustState.UNTRUSTED;
+            }
+
+            mUserTrustState.put(userId, pendingTrustState);
+        }
+        if (DEBUG) Slog.d(TAG, "pendingTrustState: " + pendingTrustState);
+
+        boolean isNowTrusted = pendingTrustState == TrustState.TRUSTED;
+        dispatchOnTrustChanged(isNowTrusted, userId, flags, getTrustGrantedMessages(userId));
+        if (isNowTrusted != wasTrusted) {
+            refreshDeviceLockedForUser(userId);
+            if (!isNowTrusted) {
+                maybeLockScreen(userId);
+            } else {
+                scheduleTrustTimeout(userId, false /* override */);
+            }
+        }
+    }
+
+
     private void updateTrustUsuallyManaged(int userId, boolean managed) {
         synchronized (mTrustUsuallyManagedForUser) {
             mTrustUsuallyManagedForUser.put(userId, managed);
@@ -472,6 +555,20 @@
         mLockPatternUtils.unlockUserWithToken(handle, token, userId);
     }
 
+    /**
+     * Locks the phone and requires some auth (not trust) like a biometric or passcode before
+     * unlocking.
+     */
+    public void lockUser(int userId) {
+        mLockPatternUtils.requireStrongAuth(
+                StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
+        try {
+            WindowManagerGlobal.getWindowManagerService().lockNow(null);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error locking screen when called from trust agent");
+        }
+    }
+
     void showKeyguardErrorMessage(CharSequence message) {
         dispatchOnTrustError(message);
     }
@@ -950,6 +1047,21 @@
         return false;
     }
 
+    private boolean aggregateIsTrustable(int userId) {
+        if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+            return false;
+        }
+        for (int i = 0; i < mActiveAgents.size(); i++) {
+            AgentInfo info = mActiveAgents.valueAt(i);
+            if (info.userId == userId) {
+                if (info.agent.isTrustable()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private List<String> getTrustGrantedMessages(int userId) {
         if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
             return new ArrayList<>();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3908874..5f04b7e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,6 +46,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -241,6 +244,7 @@
 import android.app.TaskInfo.CameraCompatControlState;
 import android.app.WaitResult;
 import android.app.WindowConfiguration;
+import android.app.admin.DevicePolicyManager;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -272,6 +276,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
 import android.net.Uri;
 import android.os.Binder;
@@ -538,6 +543,15 @@
     // Tracking splash screen status from previous activity
     boolean mSplashScreenStyleEmpty = false;
 
+    Drawable mEnterpriseThumbnailDrawable;
+
+    private void updateEnterpriseThumbnailDrawable(Context context) {
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        mEnterpriseThumbnailDrawable = dpm.getDrawable(
+                WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+                () -> context.getDrawable(R.drawable.ic_corp_badge));
+    }
+
     static final int LAUNCH_SOURCE_TYPE_SYSTEM = 1;
     static final int LAUNCH_SOURCE_TYPE_HOME = 2;
     static final int LAUNCH_SOURCE_TYPE_SYSTEMUI = 3;
@@ -716,6 +730,11 @@
     @Nullable
     private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
 
+    // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
+    // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
+    // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
+    private boolean mIsEligibleForFixedOrientationLetterbox;
+
     // State of the Camera app compat control which is used to correct stretched viewfinder
     // in apps that don't handle all possible configurations and changes between them correctly.
     @CameraCompatControlState
@@ -1925,6 +1944,8 @@
         mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
 
         mActivityRecordInputSink = new ActivityRecordInputSink(this);
+
+        updateEnterpriseThumbnailDrawable(mAtmService.mUiContext);
     }
 
     /**
@@ -6929,7 +6950,7 @@
 
     @Override
     void prepareSurfaces() {
-        final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS,
+        final boolean show = isVisible() || isAnimating(PARENTS,
                 ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
 
         if (mSurfaceControl != null) {
@@ -6988,12 +7009,11 @@
             return;
         }
         final Rect frame = win.getRelativeFrame();
-        final int thumbnailDrawableRes = task.mUserId == mWmService.mCurrentUserId
-                ? R.drawable.ic_account_circle
-                : R.drawable.ic_corp_badge;
-        final HardwareBuffer thumbnail =
-                getDisplayContent().mAppTransition
-                        .createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+        final Drawable thumbnailDrawable = task.mUserId == mWmService.mCurrentUserId
+                ? mAtmService.mUiContext.getDrawable(R.drawable.ic_account_circle)
+                : mEnterpriseThumbnailDrawable;
+        final HardwareBuffer thumbnail = getDisplayContent().mAppTransition
+                .createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
         if (thumbnail == null) {
             return;
         }
@@ -7627,6 +7647,24 @@
     }
 
     /**
+     * Whether this activity is eligible for letterbox eduction.
+     *
+     * <p>Conditions that need to be met:
+     *
+     * <ul>
+     *     <li>{@link LetterboxConfiguration#getIsEducationEnabled} is true.
+     *     <li>The activity is eligible for fixed orientation letterbox.
+     *     <li>The activity is in fullscreen.
+     * </ul>
+     */
+    // TODO(b/215316431): Add tests
+    boolean isEligibleForLetterboxEducation() {
+        return mWmService.mLetterboxConfiguration.getIsEducationEnabled()
+                && mIsEligibleForFixedOrientationLetterbox
+                && getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+    }
+
+    /**
      * In some cases, applying insets to bounds changes the orientation. For example, if a
      * close-to-square display rotates to portrait to respect a portrait orientation activity, after
      * insets such as the status and nav bars are applied, the activity may actually have a
@@ -7688,6 +7726,7 @@
     private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
             int windowingMode) {
         mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+        mIsEligibleForFixedOrientationLetterbox = false;
         final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
         final Rect stableBounds = new Rect();
         // If orientation is respected when insets are applied, then stableBounds will be empty.
@@ -7727,8 +7766,11 @@
         // make it fit the available bounds by scaling down its bounds.
         final int forcedOrientation = getRequestedConfigurationOrientation();
 
-        if (forcedOrientation == ORIENTATION_UNDEFINED
-                || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) {
+        mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
+                && forcedOrientation != parentOrientation;
+
+        if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
+                || orientationRespectedWithInsets)) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d5abe4f..56adcfd 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -93,7 +93,6 @@
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
 
-import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -101,6 +100,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
 import android.os.Binder;
 import android.os.Debug;
@@ -539,8 +539,8 @@
      * animation.
      */
     HardwareBuffer createCrossProfileAppsThumbnail(
-            @DrawableRes int thumbnailDrawableRes, Rect frame) {
-        return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+            Drawable thumbnailDrawable, Rect frame) {
+        return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
     }
 
     Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4009220..c87027d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5486,26 +5486,46 @@
     }
 
     void updateKeepClearAreas() {
+        final List<Rect> restrictedKeepClearAreas = new ArrayList();
+        final List<Rect> unrestrictedKeepClearAreas = new ArrayList();
+        getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
         mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged(
-                this, getKeepClearAreas());
+                this, restrictedKeepClearAreas, unrestrictedKeepClearAreas);
     }
 
     /**
-     * Returns all keep-clear areas from visible windows on this display.
+     * Fills {@param outRestricted} with all keep-clear areas from visible, relevant windows
+     * on this display, which set restricted keep-clear areas.
+     * Fills {@param outUnrestricted} with keep-clear areas from visible, relevant windows on this
+     * display, which set unrestricted keep-clear areas.
+     *
+     * For context on restricted vs unrestricted keep-clear areas, see
+     * {@link android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS}.
      */
-    ArrayList<Rect> getKeepClearAreas() {
-        final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+    void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) {
         final Matrix tmpMatrix = new Matrix();
         final float[] tmpFloat9 = new float[9];
         forAllWindows(w -> {
             if (w.isVisible() && !w.inPinnedWindowingMode()) {
-                keepClearAreas.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+                if (w.mSession.mSetsUnrestrictedKeepClearAreas) {
+                    outUnrestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+                } else {
+                    outRestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+                }
             }
 
             // We stop traversing when we reach the base of a fullscreen app.
             return w.getWindowType() == TYPE_BASE_APPLICATION
                     && w.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
         }, true);
+    }
+
+    /**
+     * Returns all keep-clear areas from visible, relevant windows on this display.
+     */
+    ArrayList<Rect> getKeepClearAreas() {
+        final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+        getKeepClearAreas(keepClearAreas, keepClearAreas);
         return keepClearAreas;
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index 276dbe9..e18d539 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -120,12 +120,13 @@
         mDisplayListeners.finishBroadcast();
     }
 
-    void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> keepClearAreas) {
+    void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> restricted,
+            List<Rect> unrestricted) {
         int count = mDisplayListeners.beginBroadcast();
         for (int i = 0; i < count; ++i) {
             try {
                 mDisplayListeners.getBroadcastItem(i).onKeepClearAreasChanged(
-                        display.mDisplayId, keepClearAreas);
+                        display.mDisplayId, restricted, unrestricted);
             } catch (RemoteException e) {
             }
         }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index f91969b..1f0fdcf 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -24,6 +24,7 @@
 import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.PointF;
 import android.os.Debug;
 import android.os.IBinder;
@@ -254,6 +255,25 @@
         }
     }
 
+    @Override
+    @Nullable
+    public SurfaceControl createSurfaceForGestureMonitor(String name, int displayId) {
+        synchronized (mService.mGlobalLock) {
+            final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+            if (dc == null) {
+                Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId
+                        + " - DisplayContent not found.");
+                return null;
+            }
+            return mService.makeSurfaceBuilder(dc.getSession())
+                    .setContainerLayer()
+                    .setName(name)
+                    .setCallsite("createSurfaceForGestureMonitor")
+                    .setParent(dc.getSurfaceControl())
+                    .build();
+        }
+    }
+
     /** Waits until the built-in input devices have been configured. */
     public boolean waitForInputDevicesReady(long timeoutMillis) {
         synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 10776ab..1e12173 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -32,6 +32,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
 import android.app.StatusBarManager;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -134,7 +135,7 @@
 
     /** Updates the target which can control system bars. */
     void updateBarControlTarget(@Nullable WindowState focusedWin) {
-        if (mFocusedWin != focusedWin){
+        if (mFocusedWin != focusedWin) {
             abortTransient();
         }
         mFocusedWin = focusedWin;
@@ -156,7 +157,7 @@
     }
 
     boolean isHidden(@InternalInsetsType int type) {
-        final InsetsSourceProvider provider =  mStateController.peekSourceProvider(type);
+        final InsetsSourceProvider provider = mStateController.peekSourceProvider(type);
         return provider != null && provider.hasWindow() && !provider.getSource().isVisible();
     }
 
@@ -181,6 +182,10 @@
                         mShowingTransientTypes.toArray(), isGestureOnSystemBar);
             }
             updateBarControlTarget(mFocusedWin);
+            dispatchTransientSystemBarsVisibilityChanged(
+                    mFocusedWin,
+                    isTransient(ITYPE_STATUS_BAR) || isTransient(ITYPE_NAVIGATION_BAR),
+                    isGestureOnSystemBar);
 
             // The leashes can be created while updating bar control target. The surface transaction
             // of the new leashes might not be applied yet. The callback posted here ensures we can
@@ -198,6 +203,12 @@
         if (mShowingTransientTypes.size() == 0) {
             return;
         }
+
+        dispatchTransientSystemBarsVisibilityChanged(
+                mFocusedWin,
+                /* areVisible= */ false,
+                /* wereRevealedFromSwipeOnSystemBar= */ false);
+
         startAnimation(false /* show */, () -> {
             synchronized (mDisplayContent.mWmService.mGlobalLock) {
                 for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
@@ -373,6 +384,11 @@
                     mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray());
         }
         mShowingTransientTypes.clear();
+
+        dispatchTransientSystemBarsVisibilityChanged(
+                mFocusedWin,
+                /* areVisible= */ false,
+                /* wereRevealedFromSwipeOnSystemBar= */ false);
     }
 
     private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
@@ -521,6 +537,32 @@
         listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
     }
 
+    private void dispatchTransientSystemBarsVisibilityChanged(
+            @Nullable WindowState focusedWindow,
+            boolean areVisible,
+            boolean wereRevealedFromSwipeOnSystemBar) {
+        if (focusedWindow == null) {
+            return;
+        }
+
+        Task task = focusedWindow.getTask();
+        if (task == null) {
+            return;
+        }
+
+        int taskId = task.mTaskId;
+        boolean isValidTaskId = taskId != ActivityTaskManager.INVALID_TASK_ID;
+        if (!isValidTaskId) {
+            return;
+        }
+
+        mDisplayContent.mWmService.mTaskSystemBarsListenerController
+                .dispatchTransientSystemBarVisibilityChanged(
+                        taskId,
+                        areVisible,
+                        wereRevealedFromSwipeOnSystemBar);
+    }
+
     private class BarWindow {
 
         private final int mId;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 1955e30..ad2767c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -126,6 +126,9 @@
     @LetterboxReachabilityPosition
     private volatile int mLetterboxPositionForReachability;
 
+    // Whether education is allowed for letterboxed fullscreen apps.
+    private boolean mIsEducationEnabled;
+
     LetterboxConfiguration(Context systemUiContext) {
         mContext = systemUiContext;
         mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
@@ -143,6 +146,8 @@
                 R.bool.config_letterboxIsReachabilityEnabled);
         mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
         mLetterboxPositionForReachability = mDefaultPositionForReachability;
+        mIsEducationEnabled = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsEducationEnabled);
     }
 
     /**
@@ -501,4 +506,27 @@
         mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
     }
 
+    /**
+     * Whether education is allowed for letterboxed fullscreen apps.
+     */
+    boolean getIsEducationEnabled() {
+        return mIsEducationEnabled;
+    }
+
+    /**
+     * Overrides whether education is allowed for letterboxed fullscreen apps.
+     */
+    void setIsEducationEnabled(boolean enabled) {
+        mIsEducationEnabled = enabled;
+    }
+
+    /**
+     * Resets whether education is allowed for letterboxed fullscreen apps to
+     * {@link R.bool.config_letterboxIsEducationEnabled}.
+     */
+    void resetIsEducationEnabled() {
+        mIsEducationEnabled = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsEducationEnabled);
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 76a7981..ee03d02 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -981,6 +981,29 @@
             mWmService.checkDrawnWindowsLocked();
         }
 
+        final int N = mWmService.mPendingRemove.size();
+        if (N > 0) {
+            if (mWmService.mPendingRemoveTmp.length < N) {
+                mWmService.mPendingRemoveTmp = new WindowState[N + 10];
+            }
+            mWmService.mPendingRemove.toArray(mWmService.mPendingRemoveTmp);
+            mWmService.mPendingRemove.clear();
+            ArrayList<DisplayContent> displayList = new ArrayList();
+            for (i = 0; i < N; i++) {
+                final WindowState w = mWmService.mPendingRemoveTmp[i];
+                w.removeImmediately();
+                final DisplayContent displayContent = w.getDisplayContent();
+                if (displayContent != null && !displayList.contains(displayContent)) {
+                    displayList.add(displayContent);
+                }
+            }
+
+            for (int j = displayList.size() - 1; j >= 0; --j) {
+                final DisplayContent dc = displayList.get(j);
+                dc.assignWindowLayers(true /*setLayoutNeeded*/);
+            }
+        }
+
         forAllDisplays(dc -> {
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 98acc46..2ae2b43 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
@@ -114,6 +115,7 @@
     private String mRelayoutTag;
     private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
     private final InsetsSourceControl[] mDummyControls =  new InsetsSourceControl[0];
+    final boolean mSetsUnrestrictedKeepClearAreas;
 
     public Session(WindowManagerService service, IWindowSessionCallback callback) {
         mService = service;
@@ -132,6 +134,9 @@
                         == PERMISSION_GRANTED;
         mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission(
                 START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED;
+        mSetsUnrestrictedKeepClearAreas =
+                service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
+                        == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
         mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7d06526..97735a2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3434,6 +3434,9 @@
         // Whether the direct top activity is in size compat mode on foreground.
         info.topActivityInSizeCompat = isTopActivityResumed
                 && mReuseActivitiesReport.top.inSizeCompatMode();
+        // Whether the direct top activity is eligible for letterbox education.
+        info.topActivityEligibleForLetterboxEducation = isTopActivityResumed
+                && mReuseActivitiesReport.top.isEligibleForLetterboxEducation();
         // Whether the direct top activity requested showing camera compat control.
         info.cameraCompatControlState = isTopActivityResumed
                 ? mReuseActivitiesReport.top.getCameraCompatControlState()
diff --git a/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java b/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java
new file mode 100644
index 0000000..acb6061
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
+
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+/**
+ * Manages dispatch of task system bar changes to interested listeners. All invocations must be
+ * performed while the {@link WindowManagerService#getWindowManagerLock() Window Manager Lock} is
+ * held.
+ */
+final class TaskSystemBarsListenerController {
+
+    private final HashSet<TaskSystemBarsListener> mListeners = new HashSet<>();
+    private final Executor mBackgroundExecutor;
+
+    TaskSystemBarsListenerController() {
+        this.mBackgroundExecutor = BackgroundThread.getExecutor();
+    }
+
+    void registerListener(TaskSystemBarsListener listener) {
+        mListeners.add(listener);
+    }
+
+    void unregisterListener(TaskSystemBarsListener listener) {
+        mListeners.remove(listener);
+    }
+
+    void dispatchTransientSystemBarVisibilityChanged(
+            int taskId,
+            boolean visible,
+            boolean wereRevealedFromSwipeOnSystemBar) {
+        HashSet<TaskSystemBarsListener> localListeners;
+        localListeners = new HashSet<>(mListeners);
+
+        mBackgroundExecutor.execute(() -> {
+            for (TaskSystemBarsListener listener : localListeners) {
+                listener.onTransientSystemBarsVisibilityChanged(
+                        taskId,
+                        visible,
+                        wereRevealedFromSwipeOnSystemBar);
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 20fa7a9..4900f929 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -256,6 +256,25 @@
     }
 
     /**
+     * An interface to be notified when the system bars for a task change.
+     */
+    public interface TaskSystemBarsListener {
+
+        /**
+         * Called when the visibility of the system bars of a task change.
+         *
+         * @param taskId the identifier of the task.
+         * @param visible if the transient system bars are visible.
+         * @param wereRevealedFromSwipeOnSystemBar if the transient bars were revealed due to a
+         *                                         swipe gesture on a system bar.
+         */
+        void onTransientSystemBarsVisibilityChanged(
+                int taskId,
+                boolean visible,
+                boolean wereRevealedFromSwipeOnSystemBar);
+    }
+
+    /**
      * An interface to be notified when keyguard exit animation should start.
      */
     public interface KeyguardExitAnimationStartListener {
@@ -519,6 +538,20 @@
     public abstract void registerAppTransitionListener(AppTransitionListener listener);
 
     /**
+     * Registers a listener to be notified to when the system bars of a task changes.
+     *
+     * @param listener The listener to register.
+     */
+    public abstract void registerTaskSystemBarsListener(TaskSystemBarsListener listener);
+
+    /**
+     * Registers a listener to be notified to when the system bars of a task changes.
+     *
+     * @param listener The listener to unregister.
+     */
+    public abstract void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener);
+
+    /**
      * Registers a listener to be notified to start the keyguard exit animation.
      *
      * @param listener The listener to register.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b37cb4f..1167cb5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -222,6 +222,7 @@
 import android.util.EventLog;
 import android.util.MergedConfiguration;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
@@ -586,6 +587,20 @@
     final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
 
     /**
+     * Windows whose animations have ended and now must be removed.
+     */
+    final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
+
+    /**
+     * Used when processing mPendingRemove to avoid working on the original array.
+     */
+    WindowState[] mPendingRemoveTmp = new WindowState[20];
+
+    // TODO: use WindowProcessController once go/wm-unified is done.
+    /** Mapping of process pids to configurations */
+    final SparseArray<Configuration> mProcessConfigurations = new SparseArray<>();
+
+    /**
      * Mapping of displayId to {@link DisplayImePolicy}.
      * Note that this can be accessed without holding the lock.
      */
@@ -683,6 +698,7 @@
             () -> mDisplayRotationController = null;
 
     final DisplayWindowListenerController mDisplayNotificationController;
+    final TaskSystemBarsListenerController mTaskSystemBarsListenerController;
 
     boolean mDisplayFrozen = false;
     long mDisplayFreezeTime = 0;
@@ -1265,6 +1281,7 @@
         mScreenFrozenLock.setReferenceCounted(false);
 
         mDisplayNotificationController = new DisplayWindowListenerController(this);
+        mTaskSystemBarsListenerController = new TaskSystemBarsListenerController();
 
         mActivityManager = ActivityManager.getService();
         mActivityTaskManager = ActivityTaskManager.getService();
@@ -2036,6 +2053,7 @@
             dc.mWinRemovedSinceNullFocus.add(win);
         }
         mEmbeddedWindowController.onWindowRemoved(win);
+        mPendingRemove.remove(win);
         mResizingWindows.remove(win);
         updateNonSystemOverlayWindowsVisibilityIfNeeded(win, false /* surfaceShown */);
         mWindowsChanged = true;
@@ -6364,6 +6382,23 @@
                 }
             }
         }
+        if (mPendingRemove.size() > 0) {
+            pw.println();
+            pw.println("  Remove pending for:");
+            for (int i=mPendingRemove.size()-1; i>=0; i--) {
+                WindowState w = mPendingRemove.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Remove #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
         if (mForceRemoves != null && mForceRemoves.size() > 0) {
             pw.println();
             pw.println("  Windows force removing:");
@@ -7591,6 +7626,20 @@
         }
 
         @Override
+        public void registerTaskSystemBarsListener(TaskSystemBarsListener listener) {
+            synchronized (mGlobalLock) {
+                mTaskSystemBarsListenerController.registerListener(listener);
+            }
+        }
+
+        @Override
+        public void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener) {
+            synchronized (mGlobalLock) {
+                mTaskSystemBarsListenerController.unregisterListener(listener);
+            }
+        }
+
+        @Override
         public void registerKeyguardExitAnimationStartListener(
                 KeyguardExitAnimationStartListener listener) {
             synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 0f8587c..1cf4c1b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -822,6 +822,29 @@
         return 0;
     }
 
+    private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
+        String arg = getNextArg();
+        final boolean enabled;
+        switch (arg) {
+            case "true":
+            case "1":
+                enabled = true;
+                break;
+            case "false":
+            case "0":
+                enabled = false;
+                break;
+            default:
+                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+                return -1;
+        }
+
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setIsEducationEnabled(enabled);
+        }
+        return 0;
+    }
+
     private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
         if (peekNextArg() == null) {
             getErrPrintWriter().println("Error: No arguments provided.");
@@ -859,6 +882,9 @@
                 case "--defaultPositionForReachability":
                     runSetLetterboxDefaultPositionForReachability(pw);
                     break;
+                case "--isEducationEnabled":
+                    runSetLetterboxIsEducationEnabled(pw);
+                    break;
                 default:
                     getErrPrintWriter().println(
                             "Error: Unrecognized letterbox style option: " + arg);
@@ -903,6 +929,9 @@
                     case "defaultPositionForReachability":
                         mLetterboxConfiguration.getDefaultPositionForReachability();
                         break;
+                    case "isEducationEnabled":
+                        mLetterboxConfiguration.getIsEducationEnabled();
+                        break;
                     default:
                         getErrPrintWriter().println(
                                 "Error: Unrecognized letterbox style option: " + arg);
@@ -998,6 +1027,7 @@
             mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
             mLetterboxConfiguration.resetIsReachabilityEnabled();
             mLetterboxConfiguration.resetDefaultPositionForReachability();
+            mLetterboxConfiguration.resetIsEducationEnabled();
         }
     }
 
@@ -1014,6 +1044,8 @@
             pw.println("Default position for reachability: "
                     + LetterboxConfiguration.letterboxReachabilityPositionToString(
                             mLetterboxConfiguration.getDefaultPositionForReachability()));
+            pw.println("Is education enabled: "
+                    + mLetterboxConfiguration.getIsEducationEnabled());
 
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
@@ -1154,10 +1186,12 @@
         pw.println("      --defaultPositionForReachability [left|center|right]");
         pw.println("        Default horizontal position of app window  when reachability is.");
         pw.println("        enabled.");
+        pw.println("      --isEducationEnabled [true|1|false|0]");
+        pw.println("        Whether education is allowed for letterboxed fullscreen apps.");
         pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|isReachabilityEnabled");
-        pw.println("      |defaultPositionMultiplierForReachability]");
+        pw.println("      isEducationEnabled||defaultPositionMultiplierForReachability]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
         pw.println("    If no arguments provided, all values will be reset.");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0a02b44..79c64b1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4887,20 +4887,15 @@
             if (hasSurface) {
                 mWmService.mDestroySurface.add(this);
             }
+            if (mRemoveOnExit) {
+                mWmService.mPendingRemove.add(this);
+                mRemoveOnExit = false;
+            }
         }
         mAnimatingExit = false;
         getDisplayContent().mWallpaperController.hideWallpapers(this);
     }
 
-    @Override
-    boolean handleCompleteDeferredRemoval() {
-        if (mRemoveOnExit) {
-            mRemoveOnExit = false;
-            removeImmediately();
-        }
-        return super.handleCompleteDeferredRemoval();
-    }
-
     boolean clearAnimatingFlags() {
         boolean didSomething = false;
         // We don't want to clear it out for windows that get replaced, because the
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 95ef5f7..99abf44 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -103,6 +103,7 @@
         "libappfuse",
         "libbinder_ndk",
         "libbinder",
+        "libchrome",
         "libcutils",
         "libcrypto",
         "liblog",
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 43018a9..adc91fc 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -186,7 +186,7 @@
 };
 
 /** Creates a new uinput device and assigns a file descriptor. */
-static int openUinput(const char* readableName, jint vendorId, jint productId,
+static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys,
                       DeviceType deviceType, jint screenHeight, jint screenWidth) {
     android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
     if (fd < 0) {
@@ -194,6 +194,8 @@
         return -errno;
     }
 
+    ioctl(fd, UI_SET_PHYS, phys);
+
     ioctl(fd, UI_SET_EVBIT, EV_KEY);
     ioctl(fd, UI_SET_EVBIT, EV_SYN);
     switch (deviceType) {
@@ -295,28 +297,30 @@
     return fd.release();
 }
 
-static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
+static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, jstring phys,
                          DeviceType deviceType, int screenHeight, int screenWidth) {
     ScopedUtfChars readableName(env, name);
-    return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight,
-                      screenWidth);
+    ScopedUtfChars readablePhys(env, phys);
+    return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType,
+                      screenHeight, screenWidth);
 }
 
 static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                    jint productId) {
-    return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0,
-                         /* screenWidth */ 0);
+                                    jint productId, jstring phys) {
+    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD,
+                         /* screenHeight */ 0, /* screenWidth */ 0);
 }
 
 static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                 jint productId) {
-    return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0,
-                         /* screenWidth */ 0);
+                                 jint productId, jstring phys) {
+    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::MOUSE,
+                         /* screenHeight */ 0, /* screenWidth */ 0);
 }
 
 static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                       jint productId, jint height, jint width) {
-    return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width);
+                                       jint productId, jstring phys, jint height, jint width) {
+    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::TOUCHSCREEN, height,
+                         width);
 }
 
 static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
@@ -435,9 +439,11 @@
 }
 
 static JNINativeMethod methods[] = {
-        {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard},
-        {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse},
-        {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I",
+        {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I",
+         (void*)nativeOpenUinputKeyboard},
+        {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I",
+         (void*)nativeOpenUinputMouse},
+        {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I",
          (void*)nativeOpenUinputTouchscreen},
         {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
         {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index df5fb28..8cb27e1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -457,6 +457,9 @@
     mInputManager->getReader().dump(dump);
     dump += "\n";
 
+    mInputManager->getUnwantedInteractionBlocker().dump(dump);
+    dump += "\n";
+
     mInputManager->getClassifier().dump(dump);
     dump += "\n";
 
@@ -704,6 +707,7 @@
 
 void NativeInputManager::notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) {
     ATRACE_CALL();
+    mInputManager->getUnwantedInteractionBlocker().notifyInputDevicesChanged(inputDevices);
     JNIEnv* env = jniEnv();
 
     size_t count = inputDevices.size();
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0da8f7e..166a0f5 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -244,6 +244,9 @@
     return hasLatLong(location.v1_0);
 }
 
+bool isSvStatusRegistered = false;
+bool isNmeaRegistered = false;
+
 }  // namespace
 
 static inline jboolean boolToJbool(bool value) {
@@ -505,6 +508,13 @@
 
 template <class T_list, class T_sv_info>
 Return<void> GnssCallback::gnssSvStatusCbImpl(const T_list& svStatus) {
+    // In HIDL or AIDL v1, if no listener is registered, do not report svInfoList to the framework.
+    if (gnssHalAidl == nullptr || gnssHalAidl->getInterfaceVersion() == 1) {
+        if (!isSvStatusRegistered) {
+            return Void();
+        }
+    }
+
     JNIEnv* env = getJniEnv();
 
     uint32_t listSize = getGnssSvInfoListSize(svStatus);
@@ -566,8 +576,12 @@
     return Void();
 }
 
-Return<void> GnssCallback::gnssNmeaCb(
-    int64_t timestamp, const ::android::hardware::hidl_string& nmea) {
+Return<void> GnssCallback::gnssNmeaCb(int64_t timestamp,
+                                      const ::android::hardware::hidl_string& nmea) {
+    // In HIDL, if no listener is registered, do not report nmea to the framework.
+    if (!isNmeaRegistered) {
+        return Void();
+    }
     JNIEnv* env = getJniEnv();
     /*
      * The Java code will call back to read these values.
@@ -680,6 +694,12 @@
 }
 
 Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) {
+    // In AIDL v1, if no listener is registered, do not report nmea to the framework.
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() == 1) {
+        if (!isNmeaRegistered) {
+            return Status::ok();
+        }
+    }
     JNIEnv* env = getJniEnv();
     /*
      * The Java code will call back to read these values.
@@ -1504,11 +1524,14 @@
         JNIEnv* /* env */, jclass, jint mode, jint recurrence, jint min_interval,
         jint preferred_accuracy, jint preferred_time, jboolean low_power_mode) {
     if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->setPositionMode(static_cast<IGnssAidl::GnssPositionMode>(mode),
-                                                   static_cast<IGnssAidl::GnssPositionRecurrence>(
-                                                           recurrence),
-                                                   min_interval, preferred_accuracy, preferred_time,
-                                                   low_power_mode);
+        IGnssAidl::PositionModeOptions options;
+        options.mode = static_cast<IGnssAidl::GnssPositionMode>(mode);
+        options.recurrence = static_cast<IGnssAidl::GnssPositionRecurrence>(recurrence);
+        options.minIntervalMs = min_interval;
+        options.preferredAccuracyMeters = preferred_accuracy;
+        options.preferredTimeMs = preferred_time;
+        options.lowPowerMode = low_power_mode;
+        auto status = gnssHalAidl->setPositionMode(options);
         return checkAidlStatus(status, "IGnssAidl setPositionMode() failed.");
     }
 
@@ -1559,6 +1582,58 @@
     return checkHidlReturn(result, "IGnss stop() failed.");
 }
 
+static jboolean android_location_gnss_hal_GnssNative_start_sv_status_collection(JNIEnv* /* env */,
+                                                                                jclass) {
+    isSvStatusRegistered = true;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->startSvStatus();
+        return checkAidlStatus(status, "IGnssAidl startSvStatus() failed.");
+    }
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_stop_sv_status_collection(JNIEnv* /* env */,
+                                                                               jclass) {
+    isSvStatusRegistered = false;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->stopSvStatus();
+        return checkAidlStatus(status, "IGnssAidl stopSvStatus() failed.");
+    }
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_start_nmea_message_collection(
+        JNIEnv* /* env */, jclass) {
+    isNmeaRegistered = true;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->startNmea();
+        return checkAidlStatus(status, "IGnssAidl startNmea() failed.");
+    }
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_stop_nmea_message_collection(JNIEnv* /* env */,
+                                                                                  jclass) {
+    isNmeaRegistered = false;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->stopNmea();
+        return checkAidlStatus(status, "IGnssAidl stopNmea() failed.");
+    }
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
 static void android_location_gnss_hal_GnssNative_delete_aiding_data(JNIEnv* /* env */, jclass,
                                                                     jint flags) {
     if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
@@ -1986,7 +2061,7 @@
     jfloat eplMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEpl);
     jfloat eplUncMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEplUnc);
     uint16_t corrFlags = static_cast<uint16_t>(correctionFlags);
-    jobject reflectingPlaneObj;
+    jobject reflectingPlaneObj = nullptr;
     bool has_ref_plane = (corrFlags & GnssSingleSatCorrectionFlags::HAS_REFLECTING_PLANE) != 0;
     if (has_ref_plane) {
         reflectingPlaneObj =
@@ -2010,6 +2085,7 @@
                 .azimuthDegrees = azimuthDegreeRefPlane,
         };
     }
+    env->DeleteLocalRef(reflectingPlaneObj);
 
     SingleSatCorrection_V1_0 singleSatCorrection = {
             .singleSatCorrectionFlags = corrFlags,
@@ -2041,6 +2117,7 @@
         };
 
         list[i] = singleSatCorrection_1_1;
+        env->DeleteLocalRef(singleSatCorrectionObj);
     }
 }
 
@@ -2058,6 +2135,7 @@
         singleSatCorrection.constellation = static_cast<GnssConstellationType_V1_0>(constType),
 
         list[i] = singleSatCorrection;
+        env->DeleteLocalRef(singleSatCorrectionObj);
     }
 }
 
@@ -2128,6 +2206,7 @@
 
     hidl_vec<SingleSatCorrection_V1_0> list(len);
     getSingleSatCorrectionList_1_0(env, singleSatCorrectionList, list);
+    env->DeleteLocalRef(singleSatCorrectionList);
     measurementCorrections_1_0.satCorrections = list;
 
     auto result = gnssCorrectionsIface_V1_0->setCorrections(measurementCorrections_1_0);
@@ -2362,6 +2441,16 @@
         {"native_is_gnss_visibility_control_supported", "()Z",
          reinterpret_cast<void*>(
                  android_location_gnss_hal_GnssNative_is_gnss_visibility_control_supported)},
+        {"native_start_sv_status_collection", "()Z",
+         reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_start_sv_status_collection)},
+        {"native_stop_sv_status_collection", "()Z",
+         reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_stop_sv_status_collection)},
+        {"native_start_nmea_message_collection", "()Z",
+         reinterpret_cast<void*>(
+                 android_location_gnss_hal_GnssNative_start_nmea_message_collection)},
+        {"native_stop_nmea_message_collection", "()Z",
+         reinterpret_cast<void*>(
+                 android_location_gnss_hal_GnssNative_stop_nmea_message_collection)},
 };
 
 static const JNINativeMethod sBatchingMethods[] = {
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index d760b4d..424ffd4 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -41,7 +41,7 @@
 jboolean AGnssRil::setSetId(jint type, const jstring& setid_string) {
     JNIEnv* env = getJniEnv();
     ScopedJniString jniSetId{env, setid_string};
-    auto status = mIAGnssRil->setSetId((IAGnssRil::SetIDType)type, jniSetId.c_str());
+    auto status = mIAGnssRil->setSetId((IAGnssRil::SetIdType)type, jniSetId.c_str());
     return checkAidlStatus(status, "IAGnssRilAidl setSetId() failed.");
 }
 
diff --git a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
index fbc000b..99d06eb 100644
--- a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
+++ b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
@@ -226,17 +226,18 @@
             env->NewObject(class_gnssAntennaInfoBuilder, method_gnssAntennaInfoBuilderCtor);
 
     // Set fields
-    env->CallObjectMethod(gnssAntennaInfoBuilderObject,
-                          method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
-                          gnssAntennaInfo.carrierFrequencyMHz);
-    env->CallObjectMethod(gnssAntennaInfoBuilderObject,
-                          method_gnssAntennaInfoBuilderSetPhaseCenterOffset, phaseCenterOffset);
-    env->CallObjectMethod(gnssAntennaInfoBuilderObject,
-                          method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections,
-                          phaseCenterVariationCorrections);
-    env->CallObjectMethod(gnssAntennaInfoBuilderObject,
-                          method_gnssAntennaInfoBuilderSetSignalGainCorrections,
-                          signalGainCorrections);
+    callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+                                   method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
+                                   gnssAntennaInfo.carrierFrequencyMHz);
+    callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+                                   method_gnssAntennaInfoBuilderSetPhaseCenterOffset,
+                                   phaseCenterOffset);
+    callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+                                   method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections,
+                                   phaseCenterVariationCorrections);
+    callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+                                   method_gnssAntennaInfoBuilderSetSignalGainCorrections,
+                                   signalGainCorrections);
 
     // build
     jobject gnssAntennaInfoObject =
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index fbdeec6..34ca559 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -212,13 +212,14 @@
     jobject gnssMeasurementsEventBuilderObject =
             env->NewObject(class_gnssMeasurementsEventBuilder,
                            method_gnssMeasurementsEventBuilderCtor);
-    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
-                          method_gnssMeasurementsEventBuilderSetClock, clock);
-    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
-                          method_gnssMeasurementsEventBuilderSetMeasurements, measurementArray);
-    env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
-                          method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
-                          gnssAgcArray);
+    callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+                                   method_gnssMeasurementsEventBuilderSetClock, clock);
+    callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+                                   method_gnssMeasurementsEventBuilderSetMeasurements,
+                                   measurementArray);
+    callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+                                   method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
+                                   gnssAgcArray);
     jobject gnssMeasurementsEventObject =
             env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
                                   method_gnssMeasurementsEventBuilderBuild);
@@ -359,9 +360,7 @@
     jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements);
 
     jobjectArray gnssAgcArray = nullptr;
-    if (data.gnssAgcs.has_value()) {
-        gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value());
-    }
+    gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
     setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
 
     env->DeleteLocalRef(clock);
@@ -410,24 +409,24 @@
                                        satellitePvt.satClockInfo.satHardwareCodeBiasMeters,
                                        satellitePvt.satClockInfo.satTimeCorrectionMeters,
                                        satellitePvt.satClockInfo.satClkDriftMps);
-            env->CallObjectMethod(satellitePvtBuilderObject,
-                                  method_satellitePvtBuilderSetPositionEcef, positionEcef);
-            env->CallObjectMethod(satellitePvtBuilderObject,
-                                  method_satellitePvtBuilderSetVelocityEcef, velocityEcef);
-            env->CallObjectMethod(satellitePvtBuilderObject, method_satellitePvtBuilderSetClockInfo,
-                                  clockInfo);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetPositionEcef, positionEcef);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetVelocityEcef, velocityEcef);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetClockInfo, clockInfo);
         }
 
         if (satFlags & SatellitePvt::HAS_IONO) {
-            env->CallObjectMethod(satellitePvtBuilderObject,
-                                  method_satellitePvtBuilderSetIonoDelayMeters,
-                                  satellitePvt.ionoDelayMeters);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetIonoDelayMeters,
+                                           satellitePvt.ionoDelayMeters);
         }
 
         if (satFlags & SatellitePvt::HAS_TROPO) {
-            env->CallObjectMethod(satellitePvtBuilderObject,
-                                  method_satellitePvtBuilderSetTropoDelayMeters,
-                                  satellitePvt.tropoDelayMeters);
+            callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+                                           method_satellitePvtBuilderSetTropoDelayMeters,
+                                           satellitePvt.tropoDelayMeters);
         }
 
         jobject satellitePvtObject =
@@ -455,17 +454,19 @@
             jobject correlationVectorBuilderObject =
                     env->NewObject(class_correlationVectorBuilder,
                                    method_correlationVectorBuilderCtor);
-            env->CallObjectMethod(correlationVectorBuilderObject,
-                                  method_correlationVectorBuilderSetMagnitude, magnitudeArray);
-            env->CallObjectMethod(correlationVectorBuilderObject,
-                                  method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond,
-                                  correlationVector.frequencyOffsetMps);
-            env->CallObjectMethod(correlationVectorBuilderObject,
-                                  method_correlationVectorBuilderSetSamplingStartMeters,
-                                  correlationVector.samplingStartM);
-            env->CallObjectMethod(correlationVectorBuilderObject,
-                                  method_correlationVectorBuilderSetSamplingWidthMeters,
-                                  correlationVector.samplingWidthM);
+            callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+                                           method_correlationVectorBuilderSetMagnitude,
+                                           magnitudeArray);
+            callObjectMethodIgnoringResult(
+                    env, correlationVectorBuilderObject,
+                    method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond,
+                    correlationVector.frequencyOffsetMps);
+            callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+                                           method_correlationVectorBuilderSetSamplingStartMeters,
+                                           correlationVector.samplingStartM);
+            callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+                                           method_correlationVectorBuilderSetSamplingWidthMeters,
+                                           correlationVector.samplingWidthM);
             jobject correlationVectorObject =
                     env->CallObjectMethod(correlationVectorBuilderObject,
                                           method_correlationVectorBuilderBuild);
@@ -508,8 +509,8 @@
     return gnssMeasurementArray;
 }
 
-jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(
-        JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) {
+jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(JNIEnv* env,
+                                                               const std::vector<GnssAgc>& agcs) {
     if (agcs.size() == 0) {
         return nullptr;
     }
@@ -518,18 +519,17 @@
             env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */);
 
     for (uint16_t i = 0; i < agcs.size(); ++i) {
-        if (!agcs[i].has_value()) {
-            continue;
-        }
-        const GnssAgc& gnssAgc = agcs[i].value();
+        const GnssAgc& gnssAgc = agcs[i];
 
         jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor);
-        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
-                              gnssAgc.agcLevelDb);
-        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetConstellationType,
-                              (int)gnssAgc.constellation);
-        env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetCarrierFrequencyHz,
-                              gnssAgc.carrierFrequencyHz);
+        callObjectMethodIgnoringResult(env, agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
+                                       gnssAgc.agcLevelDb);
+        callObjectMethodIgnoringResult(env, agcBuilderObject,
+                                       method_gnssAgcBuilderSetConstellationType,
+                                       (int)gnssAgc.constellation);
+        callObjectMethodIgnoringResult(env, agcBuilderObject,
+                                       method_gnssAgcBuilderSetCarrierFrequencyHz,
+                                       gnssAgc.carrierFrequencyHz);
         jobject agcObject = env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderBuild);
 
         env->SetObjectArrayElement(gnssAgcArray, i, agcObject);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index 9b346312..17af949 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -62,8 +62,8 @@
 
     jobjectArray translateAllGnssMeasurements(
             JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements);
-    jobjectArray translateAllGnssAgcs(
-            JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs);
+    jobjectArray translateAllGnssAgcs(JNIEnv* env,
+                                      const std::vector<hardware::gnss::GnssData::GnssAgc>& agcs);
 
     void translateAndSetGnssData(const hardware::gnss::GnssData& data);
 
diff --git a/services/core/jni/gnss/Utils.cpp b/services/core/jni/gnss/Utils.cpp
index 40a94ce..8f32c47 100644
--- a/services/core/jni/gnss/Utils.cpp
+++ b/services/core/jni/gnss/Utils.cpp
@@ -111,6 +111,13 @@
     }
 }
 
+void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...) {
+    va_list args;
+    va_start(args, mid);
+    env->DeleteLocalRef(env->CallObjectMethodV(obj, mid, args));
+    va_end(args);
+}
+
 JavaObject::JavaObject(JNIEnv* env, jclass clazz, jmethodID defaultCtor)
       : env_(env), clazz_(clazz) {
     object_ = env_->NewObject(clazz_, defaultCtor);
diff --git a/services/core/jni/gnss/Utils.h b/services/core/jni/gnss/Utils.h
index 2640a77..c8ee661 100644
--- a/services/core/jni/gnss/Utils.h
+++ b/services/core/jni/gnss/Utils.h
@@ -56,6 +56,8 @@
 
 void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
 
+void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...);
+
 template <class T>
 void logHidlError(hardware::Return<T>& result, const char* errorMessage) {
     ALOGE("%s HIDL transport error: %s", errorMessage, result.description().c_str());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 26c442d..e18e002 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.os.FileUtils;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
@@ -83,7 +84,7 @@
     private static final String ATTR_NEW_USER_DISCLAIMER = "new-user-disclaimer";
 
     // Values of ATTR_NEW_USER_DISCLAIMER
-    static final String NEW_USER_DISCLAIMER_SHOWN = "shown";
+    static final String NEW_USER_DISCLAIMER_ACKNOWLEDGED = "acked";
     static final String NEW_USER_DISCLAIMER_NOT_NEEDED = "not_needed";
     static final String NEW_USER_DISCLAIMER_NEEDED = "needed";
 
@@ -613,6 +614,28 @@
         }
     }
 
+    boolean isNewUserDisclaimerAcknowledged() {
+        if (mNewUserDisclaimer == null) {
+            if (mUserId == UserHandle.USER_SYSTEM) {
+                return true;
+            }
+            Slogf.w(TAG, "isNewUserDisclaimerAcknowledged(%d): mNewUserDisclaimer is null",
+                    mUserId);
+            return false;
+        }
+        switch (mNewUserDisclaimer) {
+            case NEW_USER_DISCLAIMER_ACKNOWLEDGED:
+            case NEW_USER_DISCLAIMER_NOT_NEEDED:
+                return true;
+            case NEW_USER_DISCLAIMER_NEEDED:
+                return false;
+            default:
+                Slogf.w(TAG, "isNewUserDisclaimerAcknowledged(%d): invalid value %d", mUserId,
+                        mNewUserDisclaimer);
+                return false;
+        }
+    }
+
     void dump(IndentingPrintWriter pw) {
         pw.println();
         pw.println("Enabled Device Admins (User " + mUserId + ", provisioningState: "
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 733cfcd..6f41d42 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -56,6 +56,8 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
 import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
@@ -66,9 +68,12 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
 import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
@@ -3918,8 +3923,8 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(
-                isProfileOwner(caller) || isDeviceOwner(caller) || isSystemUid(caller)
-                || isPasswordLimitingAdminTargetingP(caller));
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                        || isSystemUid(caller) || isPasswordLimitingAdminTargetingP(caller));
 
         if (parent) {
             Preconditions.checkCallAuthorization(
@@ -4772,7 +4777,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller));
         Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
 
         return !isSeparateProfileChallengeEnabled(caller.getUserId());
@@ -4860,12 +4866,12 @@
         enforceUserUnlocked(caller.getUserId());
         if (parent) {
             Preconditions.checkCallAuthorization(
-                    isDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
                     "Only profile owner, device owner and system may call this method on parent.");
         } else {
             Preconditions.checkCallAuthorization(
                     hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
-                            || isDeviceOwner(caller) || isProfileOwner(caller),
+                            || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
                     "Must have " + REQUEST_PASSWORD_COMPLEXITY
                             + " permission, or be a profile owner or device owner.");
         }
@@ -4888,7 +4894,8 @@
                 "Provided complexity is not one of the allowed values.");
 
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
@@ -4968,7 +4975,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwner(caller));
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
 
@@ -5160,7 +5167,7 @@
         }
 
         // If caller has PO (or DO) throw or fail silently depending on its target SDK level.
-        if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+        if (isDefaultDeviceOwner(caller) || isProfileOwner(caller)) {
             synchronized (getLockObject()) {
                 ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
                 if (getTargetSdk(admin.info.getPackageName(), userHandle) < Build.VERSION_CODES.O) {
@@ -5219,7 +5226,7 @@
             return false;
         }
 
-        boolean callerIsDeviceOwnerAdmin = isDeviceOwner(caller);
+        boolean callerIsDeviceOwnerAdmin = isDefaultDeviceOwner(caller);
         boolean doNotAskCredentialsOnBoot =
                 (flags & DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0;
         if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) {
@@ -5406,7 +5413,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         // timeoutMs with value 0 means that the admin doesn't participate
         // timeoutMs is clamped to the interval in case the internal constants change in the future
         final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -5575,7 +5583,8 @@
     }
 
     private boolean canManageCaCerts(CallerIdentity caller) {
-        return (caller.hasAdminComponent() && (isDeviceOwner(caller) || isProfileOwner(caller)))
+        return (caller.hasAdminComponent() && (isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))
                 || hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES);
     }
@@ -5689,7 +5698,7 @@
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
@@ -5754,7 +5763,7 @@
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(
@@ -5818,12 +5827,12 @@
     }
 
     private boolean canInstallCertificates(CallerIdentity caller) {
-        return isProfileOwner(caller) || isDeviceOwner(caller)
+        return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                 || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
     }
 
     private boolean canChooseCertificates(CallerIdentity caller) {
-        return isProfileOwner(caller) || isDeviceOwner(caller)
+        return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                 || isCallerDelegate(caller, DELEGATION_CERT_SELECTION);
     }
 
@@ -5871,7 +5880,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_SELECTION)));
 
         final int granteeUid;
@@ -5984,7 +5993,7 @@
 
         // If not, fall back to the device owner check.
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+                isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
     }
 
     @VisibleForTesting
@@ -6047,7 +6056,7 @@
             enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
         } else {
             Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                     || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
             if (isCredentialManagementApp) {
                 Preconditions.checkCallAuthorization(
@@ -6182,7 +6191,7 @@
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(
@@ -6337,14 +6346,15 @@
         final int userId = caller.getUserId();
         // Ensure calling process is device/profile owner.
         if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                     || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
         } else if (!Collections.disjoint(
                 scopes, DEVICE_OWNER_OR_ORGANIZATION_OWNED_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                     || isProfileOwnerOfOrganizationOwnedDevice(caller));
         } else {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
         synchronized (getLockObject()) {
@@ -6434,7 +6444,8 @@
         // * Either it's a profile owner / device owner, if componentName is provided
         // * Or it's an app querying its own delegation scopes
         if (caller.hasAdminComponent()) {
-            Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         } else {
             Preconditions.checkCallAuthorization(isPackage(caller, delegatePackage),
                     String.format("Caller with uid %d is not %s", caller.getUid(),
@@ -6467,7 +6478,8 @@
 
         // Retrieve the user ID of the calling process.
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         synchronized (getLockObject()) {
             return getDelegatePackagesInternalLocked(scope, caller.getUserId());
         }
@@ -6600,7 +6612,8 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         // Ensure calling process is device/profile owner.
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final DevicePolicyData policy = getUserData(caller.getUserId());
@@ -6712,7 +6725,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE);
 
         if (vpnPackage == null) {
@@ -6792,7 +6806,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId()));
@@ -6818,7 +6833,8 @@
             caller = getCallerIdentity();
         } else {
             caller = getCallerIdentity(admin);
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
         return mInjector.binderWithCleanCallingIdentity(
@@ -6841,7 +6857,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId()));
@@ -6946,8 +6963,9 @@
                             + "organization-owned device.");
         }
         if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                            || calledByProfileOwnerOnOrgOwnedDevice,
+            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                            || calledByProfileOwnerOnOrgOwnedDevice
+                            || isFinancedDeviceOwner(caller),
                     "Only device owners or profile owners of organization-owned device can set "
                             + "WIPE_RESET_PROTECTION_DATA");
         }
@@ -7139,8 +7157,8 @@
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
         CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager
                 .OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
 
@@ -7189,7 +7207,8 @@
                         UserHandle.getUserId(frpManagementAgentUid));
             } else {
                 Preconditions.checkCallAuthorization(
-                        isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+                        isDefaultDeviceOwner(caller)
+                                || isProfileOwnerOfOrganizationOwnedDevice(caller));
                 admin = getProfileOwnerOrDeviceOwnerLocked(caller);
             }
         }
@@ -7617,7 +7636,7 @@
     public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkAllUsersAreAffiliatedWithDevice();
         mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
@@ -7915,7 +7934,8 @@
         }
 
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7934,8 +7954,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isProfileOwner(caller)
-                        || isDeviceOwner(caller)
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
 
         synchronized (getLockObject()) {
@@ -7955,7 +7974,8 @@
         }
 
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7974,8 +7994,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isProfileOwner(caller)
-                        || isDeviceOwner(caller)
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
 
         synchronized (getLockObject()) {
@@ -8067,7 +8086,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -8091,7 +8110,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
     }
@@ -8108,7 +8127,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -8132,7 +8151,7 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
     }
@@ -8161,7 +8180,7 @@
         // which could still contain data related to that user. Should we disallow that, e.g. until
         // next boot? Might not be needed given that this still requires user consent.
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkAllUsersAreAffiliatedWithDevice();
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REQUEST_BUGREPORT);
 
@@ -8472,7 +8491,8 @@
         }
         Objects.requireNonNull(packageList, "packageList is null");
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  isDefaultDeviceOwner(caller))
                 || (caller.hasPackage()
                 && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES);
@@ -8501,7 +8521,8 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  isDefaultDeviceOwner(caller))
                 || (caller.hasPackage()
                 && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
 
@@ -8615,8 +8636,8 @@
     @Override
     public boolean hasDeviceOwner() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                        || canManageUsers(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                        || canManageUsers(caller) || isFinancedDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return mOwners.hasDeviceOwner();
     }
@@ -8633,17 +8654,29 @@
         }
     }
 
-    private boolean isDeviceOwner(CallerIdentity caller) {
+    /**
+     * Returns {@code true} <b>only if</b> the caller is the device owner and the device owner type
+     * is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}. {@code false} is returned for the
+     * case where the caller is not the device owner, there is no device owner, or the device owner
+     * type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}.
+     *
+     */
+    private boolean isDefaultDeviceOwner(CallerIdentity caller) {
         synchronized (getLockObject()) {
-            if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
-                return false;
-            }
+            return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+                    mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT;
+        }
+    }
 
-            if (caller.hasAdminComponent()) {
-                return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
-            } else {
-                return isUidDeviceOwnerLocked(caller.getUid());
-            }
+    private boolean isDeviceOwnerLocked(CallerIdentity caller) {
+        if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
+            return false;
+        }
+
+        if (caller.hasAdminComponent()) {
+            return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
+        } else {
+            return isUidDeviceOwnerLocked(caller.getUid());
         }
     }
 
@@ -9073,8 +9106,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null));
@@ -9258,7 +9291,8 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         final int userId = caller.getUserId();
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         Preconditions.checkCallingUser(isManagedProfile(userId));
 
         synchronized (getLockObject()) {
@@ -9289,7 +9323,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         mInjector.binderWithCleanCallingIdentity(() -> {
             mUserManager.setUserName(caller.getUserId(), profileName);
@@ -9725,7 +9760,8 @@
     }
 
     private void enforceCanCallLockTaskLocked(CallerIdentity caller) {
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller)
+                || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         final int userId =  caller.getUserId();
         if (!canUserUseLockTaskLocked(userId)) {
@@ -9982,7 +10018,8 @@
             ComponentName activity) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller)
+                || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
@@ -10009,7 +10046,8 @@
     public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller)
+                || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
@@ -10030,7 +10068,7 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                 || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
         if (parent) {
             mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -10070,7 +10108,7 @@
             String packageName, Bundle settings) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
 
@@ -10168,7 +10206,8 @@
     public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER);
 
         synchronized (getLockObject()) {
@@ -10193,7 +10232,8 @@
     public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         int callingUserId = caller.getUserId();
         synchronized (getLockObject()) {
             long id = mInjector.binderClearCallingIdentity();
@@ -10242,7 +10282,8 @@
     public void clearCrossProfileIntentFilters(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         int callingUserId = caller.getUserId();
         synchronized (getLockObject()) {
@@ -10382,7 +10423,8 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -10495,7 +10537,8 @@
                             + "system input methods when called on the parent instance of an "
                             + "organization-owned device");
         } else {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
         if (packageList != null) {
@@ -10553,7 +10596,8 @@
         if (calledOnParentInstance) {
             Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
         } else {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
 
         synchronized (getLockObject()) {
@@ -10732,7 +10776,7 @@
         // Only allow the system user to use this method
         Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
                 "createAndManageUser was called from non-system user");
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
 
         final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
@@ -10934,10 +10978,11 @@
     @Override
     public void acknowledgeNewUserDisclaimer() {
         CallerIdentity callerIdentity = getCallerIdentity();
-        canManageUsers(callerIdentity);
+        Preconditions.checkCallAuthorization(canManageUsers(callerIdentity)
+                || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
 
         setShowNewUserDisclaimer(callerIdentity.getUserId(),
-                DevicePolicyData.NEW_USER_DISCLAIMER_SHOWN);
+                DevicePolicyData.NEW_USER_DISCLAIMER_ACKNOWLEDGED);
     }
 
     private void setShowNewUserDisclaimer(@UserIdInt int userId, String value) {
@@ -10970,11 +11015,23 @@
     }
 
     @Override
+    public boolean isNewUserDisclaimerAcknowledged() {
+        CallerIdentity callerIdentity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(canManageUsers(callerIdentity)
+                || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
+        int userId = callerIdentity.getUserId();
+        synchronized (getLockObject()) {
+            DevicePolicyData policyData = getUserData(userId);
+            return policyData.isNewUserDisclaimerAcknowledged();
+        }
+    }
+
+    @Override
     public boolean removeUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_USER);
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11008,7 +11065,7 @@
     public boolean switchUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER);
 
         boolean switched = false;
@@ -11083,7 +11140,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND);
 
         final int userId = userHandle.getIdentifier();
@@ -11119,7 +11176,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_STOP_USER);
 
         final int userId = userHandle.getIdentifier();
@@ -11135,7 +11192,8 @@
     public int logoutUser(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOGOUT_USER);
 
         final int callingUserId = caller.getUserId();
@@ -11165,8 +11223,8 @@
     @Override
     public int logoutUserInternal() {
         CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(
-                canManageUsers(caller) || hasCallingOrSelfPermission(permission.CREATE_USERS));
+        Preconditions.checkCallAuthorization(canManageUsers(caller)
+                || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
 
         int result = logoutUserUnchecked(getCurrentForegroundUserId());
         Slogf.d(LOG_TAG, "logout called by uid %d. Result: %d", caller.getUid(), result);
@@ -11224,7 +11282,7 @@
     public List<UserHandle> getSecondaryUsers(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             final List<UserInfo> userInfos = mInjector.getUserManager().getAliveUsers();
@@ -11244,7 +11302,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getUserManager().isUserEphemeral(caller.getUserId()));
@@ -11255,7 +11314,7 @@
             String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11306,7 +11365,7 @@
         Objects.requireNonNull(packageNames, "array of packages cannot be null");
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
 
@@ -11369,7 +11428,7 @@
     public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
 
         synchronized (getLockObject()) {
@@ -11390,8 +11449,8 @@
     public List<String> listPolicyExemptApps() {
         CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller)
-                        || isProfileOwner(caller));
+                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
+                        || isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return listPolicyExemptAppsUnchecked();
     }
@@ -11432,12 +11491,19 @@
             final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
                     getProfileOwnerOrDeviceOwnerLocked(caller), parent);
 
-            if (isDeviceOwner(caller)) {
+            if (isDefaultDeviceOwner(caller)) {
                 if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
                     throw new SecurityException("Device owner cannot set user restriction " + key);
                 }
                 Preconditions.checkArgument(!parent,
                         "Cannot use the parent instance in Device Owner mode");
+            } else if (isFinancedDeviceOwner(caller)) {
+                if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+                    throw new SecurityException("Cannot set user restriction " + key
+                            + " when managing a financed device");
+                }
+                Preconditions.checkArgument(!parent,
+                        "Cannot use the parent instance in Financed Device Owner mode");
             } else {
                 boolean profileOwnerCanChangeOnItself = !parent
                         && UserRestrictionsUtils.canProfileOwnerChange(key, userHandle);
@@ -11546,7 +11612,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller)
                 || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
 
         synchronized (getLockObject()) {
@@ -11561,7 +11628,7 @@
             boolean hidden, boolean parent) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
 
         List<String> exemptApps = listPolicyExemptAppsUnchecked();
@@ -11607,7 +11674,7 @@
             String packageName, boolean parent) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
 
         final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
@@ -11643,7 +11710,7 @@
     public void enableSystemApp(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
 
         synchronized (getLockObject()) {
@@ -11687,7 +11754,7 @@
     public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
 
         int numberOfAppsInstalled = 0;
@@ -11756,7 +11823,7 @@
             String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage()
                 && isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE)));
 
@@ -11872,7 +11939,8 @@
             boolean uninstallBlocked) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                || isFinancedDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)));
 
         final int userId = caller.getUserId();
@@ -11913,7 +11981,8 @@
             if (who != null) {
                 final CallerIdentity caller = getCallerIdentity(who);
                 Preconditions.checkCallAuthorization(
-                        isProfileOwner(caller) || isDeviceOwner(caller));
+                        isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                                || isFinancedDeviceOwner(caller));
             }
             try {
                 return mIPackageManager.getBlockUninstallForUser(packageName, userId);
@@ -12087,7 +12156,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12111,7 +12181,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12136,7 +12207,8 @@
 
         // Check can set secondary lockscreen enabled
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
                 "User %d is not allowed to call setSecondaryLockscreenEnabled",
                         caller.getUserId());
@@ -12325,6 +12397,7 @@
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
+            enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
             setLockTaskFeaturesLocked(userHandle, flags);
         }
@@ -12373,6 +12446,24 @@
         });
     }
 
+    private void enforceCanSetLockTaskFeaturesOnFinancedDevice(CallerIdentity caller, int flags) {
+        int allowedFlags = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+                | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+                | LOCK_TASK_FEATURE_NOTIFICATIONS;
+
+        if (!isFinancedDeviceOwner(caller)) {
+            return;
+        }
+
+        if ((flags == 0) || ((flags & ~(allowedFlags)) != 0)) {
+            throw new SecurityException(
+                    "Permitted lock task features when managing a financed device: "
+                            + "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, "
+                            + "LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, "
+                            + "or LOCK_TASK_FEATURE_NOTIFICATIONS");
+        }
+    }
+
     @Override
     public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
         Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
@@ -12413,7 +12504,7 @@
     public void setGlobalSetting(ComponentName who, String setting, String value) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING)
@@ -12454,7 +12545,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkStringNotEmpty(setting, "String setting is null or empty");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING);
 
         synchronized (getLockObject()) {
@@ -12476,8 +12568,8 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -12498,8 +12590,8 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -12510,7 +12602,7 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         UserHandle userHandle = caller.getUserHandle();
         if (mIsAutomotive && !locationEnabled) {
@@ -12592,8 +12684,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         // Don't allow set time when auto time is on.
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -12612,8 +12704,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         // Don't allow set timezone when auto timezone is on.
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -12633,7 +12725,8 @@
     public void setSecureSetting(ComponentName who, String setting, String value) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         int callingUserId = caller.getUserId();
         synchronized (getLockObject()) {
@@ -12720,7 +12813,8 @@
     public void setMasterVolumeMuted(ComponentName who, boolean on) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED);
 
         synchronized (getLockObject()) {
@@ -12737,7 +12831,8 @@
     public boolean isMasterVolumeMuted(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             AudioManager audioManager =
@@ -12750,7 +12845,8 @@
     public void setUserIcon(ComponentName who, Bitmap icon) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             mInjector.binderWithCleanCallingIdentity(
@@ -12766,7 +12862,8 @@
     public boolean setKeyguardDisabled(ComponentName who, boolean disabled) {
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         final int userId = caller.getUserId();
         synchronized (getLockObject()) {
@@ -12808,7 +12905,8 @@
     @Override
     public boolean setStatusBarDisabled(ComponentName who, boolean disabled) {
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
@@ -13042,7 +13140,7 @@
 
         @Override
         public boolean isActiveDeviceOwner(int uid) {
-            return isDeviceOwner(new CallerIdentity(uid, null, null));
+            return isDefaultDeviceOwner(new CallerIdentity(uid, null, null));
         }
 
         @Override
@@ -13633,7 +13731,7 @@
 
         synchronized (getLockObject()) {
             Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)
-                    || isDeviceOwner(caller));
+                    || isDefaultDeviceOwner(caller));
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
 
             if (policy == null) {
@@ -13831,7 +13929,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return mOwners.getSystemUpdateInfo();
     }
@@ -13840,7 +13939,7 @@
     public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy) {
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY);
 
@@ -13882,11 +13981,15 @@
 
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                || isFinancedDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE);
 
         synchronized (getLockObject()) {
+            if (isFinancedDeviceOwner(caller)) {
+                enforceCanSetPermissionGrantOnFinancedDevice(packageName, permission);
+            }
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13946,12 +14049,23 @@
         }
     }
 
+    private void enforceCanSetPermissionGrantOnFinancedDevice(
+            String packageName, String permission) {
+        if (!Manifest.permission.READ_PHONE_STATE.equals(permission)) {
+            throw new SecurityException("Cannot grant " + permission
+                    + " when managing a financed device");
+        } else if (!mOwners.getDeviceOwnerPackageName().equals(packageName)) {
+            throw new SecurityException("Cannot grant permission to a package that is not"
+                    + " the device owner");
+        }
+    }
+
     @Override
     public int getPermissionGrantState(ComponentName admin, String callerPackage,
             String packageName, String permission) throws RemoteException {
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
         Preconditions.checkCallAuthorization(isSystemUid(caller) || (caller.hasAdminComponent()
-                && (isProfileOwner(caller) || isDeviceOwner(caller)))
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
 
         synchronized (getLockObject()) {
@@ -14231,7 +14345,7 @@
     }
 
     private void checkIsDeviceOwner(CallerIdentity caller) {
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller), caller.getUid()
                 + " is not device owner");
     }
 
@@ -14263,8 +14377,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -14299,7 +14413,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         return isManagedProfile(caller.getUserId());
     }
@@ -14308,7 +14423,7 @@
     public void reboot(ComponentName admin) {
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REBOOT);
         mInjector.binderWithCleanCallingIdentity(() -> {
             // Make sure there are no ongoing calls on the device.
@@ -14542,7 +14657,8 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || canManageUsers(caller) || isFinancedDeviceOwner(caller));
         synchronized (getLockObject()) {
             final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
             return deviceOwnerAdmin == null ? null : deviceOwnerAdmin.organizationName;
@@ -14576,7 +14692,8 @@
         Objects.requireNonNull(who);
         Objects.requireNonNull(packageNames);
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller),
                 "Admin %s does not own the profile", caller.getComponentName());
 
         if (!mHasFeature) {
@@ -14627,7 +14744,8 @@
             return new ArrayList<>();
         }
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller),
                 "Admin %s does not own the profile", caller.getComponentName());
 
         synchronized (getLockObject()) {
@@ -14766,7 +14884,8 @@
 
         final Set<String> affiliationIds = new ArraySet<>(ids);
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
         final int callingUserId = caller.getUserId();
 
         synchronized (getLockObject()) {
@@ -14797,7 +14916,8 @@
 
         Objects.requireNonNull(admin);
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             return new ArrayList<String>(getUserData(caller.getUserId()).mAffiliationIds);
@@ -14891,7 +15011,7 @@
             if (admin != null) {
                 Preconditions.checkCallAuthorization(
                         isProfileOwnerOfOrganizationOwnedDevice(caller)
-                        || isDeviceOwner(caller));
+                        || isDefaultDeviceOwner(caller));
             } else {
                 // A delegate app passes a null admin component, which is expected
                 Preconditions.checkCallAuthorization(
@@ -14928,7 +15048,7 @@
                 if (admin != null) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            || isDeviceOwner(caller));
+                            || isDefaultDeviceOwner(caller));
                 } else {
                     // A delegate app passes a null admin component, which is expected
                     Preconditions.checkCallAuthorization(
@@ -14961,7 +15081,7 @@
         if (admin != null) {
             Preconditions.checkCallAuthorization(
                     isProfileOwnerOfOrganizationOwnedDevice(caller)
-                    || isDeviceOwner(caller));
+                    || isDefaultDeviceOwner(caller));
         } else {
             // A delegate app passes a null admin component, which is expected
             Preconditions.checkCallAuthorization(
@@ -15007,7 +15127,7 @@
         if (admin != null) {
             Preconditions.checkCallAuthorization(
                     isProfileOwnerOfOrganizationOwnedDevice(caller)
-                    || isDeviceOwner(caller));
+                    || isDefaultDeviceOwner(caller));
         } else {
             // A delegate app passes a null admin component, which is expected
             Preconditions.checkCallAuthorization(
@@ -15246,7 +15366,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
 
         toggleBackupServiceActive(caller.getUserId(), enabled);
     }
@@ -15259,7 +15380,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             synchronized (getLockObject()) {
@@ -15334,7 +15456,8 @@
         }
         Objects.requireNonNull(admin);
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final int callingUserId = caller.getUserId();
@@ -15462,7 +15585,7 @@
         final boolean isManagedProfileOwner = isProfileOwner(caller)
                 && isManagedProfile(caller.getUserId());
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                && (isDeviceOwner(caller) || isManagedProfileOwner))
+                && (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
 
         synchronized (getLockObject()) {
@@ -15622,7 +15745,7 @@
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                &&  (isDeviceOwner(caller)
+                &&  (isDefaultDeviceOwner(caller)
                 || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
                 || hasCallingOrSelfPermission(permission.MANAGE_USERS));
@@ -15654,7 +15777,7 @@
         final boolean isManagedProfileOwner = isProfileOwner(caller)
                 && isManagedProfile(caller.getUserId());
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                &&  (isDeviceOwner(caller) || isManagedProfileOwner))
+                &&  (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
         if (mOwners.hasDeviceOwner()) {
             checkAllUsersAreAffiliatedWithDevice();
@@ -15815,14 +15938,16 @@
     @Override
     public long getLastSecurityLogRetrievalTime() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || canManageUsers(caller));
         return getUserData(UserHandle.USER_SYSTEM).mLastSecurityLogRetrievalTime;
      }
 
     @Override
     public long getLastBugReportRequestTime() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || canManageUsers(caller));
         return getUserData(UserHandle.USER_SYSTEM).mLastBugReportRequestTime;
      }
 
@@ -15830,7 +15955,7 @@
     public long getLastNetworkLogRetrievalTime() {
         final CallerIdentity caller = getCallerIdentity();
 
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                 || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
                 || canManageUsers(caller));
         final int affectedUserId = getNetworkLoggingAffectedUser();
@@ -15846,7 +15971,8 @@
             throw new IllegalArgumentException("token must be at least 32-byte long");
         }
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final int userHandle = caller.getUserId();
@@ -15870,7 +15996,8 @@
             return false;
         }
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final int userHandle = caller.getUserId();
@@ -15895,7 +16022,8 @@
             return false;
         }
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             return isResetPasswordTokenActiveForUserLocked(caller.getUserId());
@@ -15920,7 +16048,8 @@
         Objects.requireNonNull(token);
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             DevicePolicyData policy = getUserData(caller.getUserId());
@@ -15945,7 +16074,7 @@
     @Override
     public boolean isCurrentInputMethodSetByOwner() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
                 || isProfileOwner(caller) || isSystemUid(caller),
                 "Only profile owner, device owner and system may call this method.");
         return getUserData(caller.getUserId()).mCurrentInputMethodSet;
@@ -15956,7 +16085,7 @@
         final int userId = user.getIdentifier();
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization((userId == caller.getUserId())
-                || isProfileOwner(caller) || isDeviceOwner(caller)
+                || isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                 || hasFullCrossUsersPermission(caller, userId));
 
         synchronized (getLockObject()) {
@@ -15973,7 +16102,8 @@
         Objects.requireNonNull(callback, "callback is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA);
 
         long ident = mInjector.binderClearCallingIdentity();
@@ -16005,7 +16135,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED);
 
         synchronized (getLockObject()) {
@@ -16054,7 +16184,8 @@
                 "Provided administrator and target have the same package name.");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         final int callingUserId = caller.getUserId();
         final DevicePolicyData policy = getUserData(callingUserId);
@@ -16096,7 +16227,7 @@
                     if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
                         notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
                     }
-                } else if (isDeviceOwner(caller)) {
+                } else if (isDefaultDeviceOwner(caller)) {
                     ownerType = ADMIN_TYPE_DEVICE_OWNER;
                     prepareTransfer(admin, target, bundle, callingUserId,
                             ADMIN_TYPE_DEVICE_OWNER);
@@ -16177,7 +16308,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         final String startUserSessionMessageString =
                 startUserSessionMessage != null ? startUserSessionMessage.toString() : null;
@@ -16202,7 +16333,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         final String endUserSessionMessageString =
                 endUserSessionMessage != null ? endUserSessionMessage.toString() : null;
@@ -16227,7 +16358,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16242,7 +16373,7 @@
         }
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16258,7 +16389,8 @@
     @Nullable
     public PersistableBundle getTransferOwnershipBundle() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             final int callingUserId = caller.getUserId();
@@ -16288,7 +16420,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
         if (tm != null) {
@@ -16309,7 +16441,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         if (apnId < 0) {
             return false;
@@ -16331,7 +16463,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         return removeOverrideApnUnchecked(apnId);
     }
 
@@ -16352,7 +16484,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         return getOverrideApnsUnchecked();
     }
 
@@ -16373,7 +16505,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED);
 
         setOverrideApnsEnabledUnchecked(enabled);
@@ -16393,7 +16525,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity(
                 () -> mContext.getContentResolver().query(
@@ -16476,7 +16608,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkAllUsersAreAffiliatedWithDevice();
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS);
 
@@ -16515,7 +16647,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
 
         final int currentMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
         switch (currentMode) {
@@ -16537,7 +16669,7 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
     }
 
@@ -16547,8 +16679,8 @@
         Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
 
         DevicePolicyEventLogger
@@ -16923,7 +17055,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(packages, "packages is null");
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(
                 DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
 
@@ -16942,7 +17075,8 @@
         Objects.requireNonNull(who, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             return mOwners.getDeviceOwnerProtectedPackages(who.getPackageName());
@@ -16954,7 +17088,7 @@
         Objects.requireNonNull(who, "Admin component name must be provided");
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "Common Criteria mode can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
         synchronized (getLockObject()) {
@@ -16974,7 +17108,7 @@
         if (who != null) {
             final CallerIdentity caller = getCallerIdentity(who);
             Preconditions.checkCallAuthorization(
-                    isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                     "Common Criteria mode can only be controlled by a device owner or "
                             + "a profile owner on an organization-owned device.");
 
@@ -17446,7 +17580,7 @@
 
         final CallerIdentity caller = getCallerIdentity(callerPackage);
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwner(caller)
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller)
                         || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
 
         synchronized (getLockObject()) {
@@ -17467,7 +17601,7 @@
 
         final CallerIdentity caller = getCallerIdentity(callerPackage);
         // Only the DPC can set this ID.
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller),
                 "Only a Device Owner or Profile Owner may set the Enterprise ID.");
         // Empty enterprise ID must not be provided in calls to this method.
         Preconditions.checkArgument(!TextUtils.isEmpty(organizationId),
@@ -17559,6 +17693,13 @@
                     ? Collections.emptySet()
                     : mOverlayPackagesProvider.getNonRequiredApps(
                             admin, caller.getUserId(), ACTION_PROVISION_MANAGED_PROFILE);
+            if (nonRequiredApps.isEmpty()) {
+                Slogf.i(LOG_TAG, "No disallowed packages for the managed profile.");
+            } else {
+                for (String packageName : nonRequiredApps) {
+                    Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]");
+                }
+            }
             userInfo = mUserManager.createProfileForUserEvenWhenDisallowed(
                     provisioningParams.getProfileName(),
                     UserManager.USER_TYPE_PROFILE_MANAGED,
@@ -17577,6 +17718,8 @@
                     startTime,
                     callerPackage);
 
+            onCreateAndProvisionManagedProfileStarted(provisioningParams);
+
             installExistingAdminPackage(userInfo.id, admin.getPackageName());
             if (!enableAdminAndSetProfileOwner(
                     userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) {
@@ -17597,6 +17740,8 @@
                 }
             }
 
+            onCreateAndProvisionManagedProfileCompleted(provisioningParams);
+
             sendProvisioningCompletedBroadcast(
                     userInfo.id,
                     ACTION_PROVISION_MANAGED_PROFILE,
@@ -17618,6 +17763,29 @@
         }
     }
 
+    /**
+     * Callback called at the beginning of {@link #createAndProvisionManagedProfile(
+     * ManagedProfileProvisioningParams, String)} after the relevant prechecks have passed.
+     *
+     * <p>The logic in this method blocks provisioning.
+     *
+     * <p>This method is meant to be overridden by OEMs.
+     */
+    private void onCreateAndProvisionManagedProfileStarted(
+            ManagedProfileProvisioningParams provisioningParams) {}
+
+    /**
+     * Callback called at the end of {@link #createAndProvisionManagedProfile(
+     * ManagedProfileProvisioningParams, String)} after all the other provisioning tasks
+     * have completed successfully.
+     *
+     * <p>The logic in this method blocks provisioning.
+     *
+     * <p>This method is meant to be overridden by OEMs.
+     */
+    private void onCreateAndProvisionManagedProfileCompleted(
+            ManagedProfileProvisioningParams provisioningParams) {}
+
     private void resetInteractAcrossProfilesAppOps() {
         mInjector.getCrossProfileApps().clearInteractAcrossProfilesAppOps();
         pregrantDefaultInteractAcrossProfilesAppOps();
@@ -17857,6 +18025,7 @@
                         ERROR_PRE_CONDITION_FAILED,
                         "Provisioning preconditions failed with result: " + result);
             }
+            onProvisionFullyManagedDeviceStarted(provisioningParams);
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
@@ -17882,6 +18051,7 @@
             disallowAddUser();
             setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
                     provisioningParams.canDeviceOwnerGrantSensorsPermissions());
+            onProvisionFullyManagedDeviceCompleted(provisioningParams);
             sendProvisioningCompletedBroadcast(
                     deviceOwnerUserId,
                     ACTION_PROVISION_MANAGED_DEVICE,
@@ -17897,6 +18067,29 @@
         }
     }
 
+    /**
+     * Callback called at the beginning of {@link #provisionFullyManagedDevice(
+     * FullyManagedDeviceProvisioningParams, String)} after the relevant prechecks have passed.
+     *
+     * <p>The logic in this method blocks provisioning.
+     *
+     * <p>This method is meant to be overridden by OEMs.
+     */
+    private void onProvisionFullyManagedDeviceStarted(
+            FullyManagedDeviceProvisioningParams provisioningParams) {}
+
+    /**
+     * Callback called at the end of {@link #provisionFullyManagedDevice(
+     * FullyManagedDeviceProvisioningParams, String)} after all the other provisioning tasks
+     * have completed successfully.
+     *
+     * <p>The logic in this method blocks provisioning.
+     *
+     * <p>This method is meant to be overridden by OEMs.
+     */
+    private void onProvisionFullyManagedDeviceCompleted(
+            FullyManagedDeviceProvisioningParams provisioningParams) {}
+
     private void setTimeAndTimezone(String timeZone, long localTime) {
         try {
             final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
@@ -18141,27 +18334,55 @@
             @DeviceOwnerType int deviceOwnerType) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
                 permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
-        verifyDeviceOwnerTypePreconditions(admin);
-
-        final String packageName = admin.getPackageName();
-        Preconditions.checkState(!mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName),
-                "The device owner type has already been set for " + packageName);
 
         synchronized (getLockObject()) {
-            mOwners.setDeviceOwnerType(packageName, deviceOwnerType);
+            setDeviceOwnerTypeLocked(admin, deviceOwnerType);
         }
     }
 
+    private void setDeviceOwnerTypeLocked(ComponentName admin,
+            @DeviceOwnerType int deviceOwnerType) {
+        String packageName = admin.getPackageName();
+        boolean isAdminTestOnly;
+
+        verifyDeviceOwnerTypePreconditionsLocked(admin);
+
+        isAdminTestOnly = isAdminTestOnlyLocked(admin, mOwners.getDeviceOwnerUserId());
+        Preconditions.checkState(isAdminTestOnly
+                        || !mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName),
+                "Test only admins can only set the device owner type more than once");
+
+        mOwners.setDeviceOwnerType(packageName, deviceOwnerType, isAdminTestOnly);
+    }
+
     @Override
     @DeviceOwnerType
     public int getDeviceOwnerType(@NonNull ComponentName admin) {
-        verifyDeviceOwnerTypePreconditions(admin);
         synchronized (getLockObject()) {
-            return mOwners.getDeviceOwnerType(admin.getPackageName());
+            verifyDeviceOwnerTypePreconditionsLocked(admin);
+            return getDeviceOwnerTypeLocked(admin.getPackageName());
         }
     }
 
-    private void verifyDeviceOwnerTypePreconditions(@NonNull ComponentName admin) {
+    @DeviceOwnerType
+    private int getDeviceOwnerTypeLocked(String packageName) {
+        return mOwners.getDeviceOwnerType(packageName);
+    }
+
+    /**
+     * {@code true} is returned <b>only if</b> the caller is the device owner and the device owner
+     * type is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}. {@code false} is returned for
+     * the case where the caller is not the device owner, there is no device owner, or the device
+     * owner type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+     */
+    private boolean isFinancedDeviceOwner(CallerIdentity caller) {
+        synchronized (getLockObject()) {
+            return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+                    mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_FINANCED;
+        }
+    }
+
+    private void verifyDeviceOwnerTypePreconditionsLocked(@NonNull ComponentName admin) {
         Preconditions.checkState(mOwners.hasDeviceOwner(), "there is no device owner");
         Preconditions.checkState(mOwners.getDeviceOwnerComponent().equals(admin),
                 "admin is not the device owner");
@@ -18172,7 +18393,7 @@
         Objects.requireNonNull(packageName, "Admin package name must be provided");
         final CallerIdentity caller = getCallerIdentity(packageName);
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "USB data signaling can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
         Preconditions.checkState(canUsbDataSignalingBeDisabled(),
@@ -18213,7 +18434,7 @@
         synchronized (getLockObject()) {
             // If the caller is an admin, return the policy set by itself. Otherwise
             // return the device-wide policy.
-            if (isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+            if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
                 return getProfileOwnerOrDeviceOwnerLocked(caller).mUsbDataSignalingEnabled;
             } else {
                 return isUsbDataSignalingEnabledInternalLocked();
@@ -18254,7 +18475,7 @@
     public void setMinimumRequiredWifiSecurityLevel(int level) {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "Wi-Fi minimum security level can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
 
@@ -18284,7 +18505,7 @@
     public void setSsidAllowlist(List<String> ssids) {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "SSID allowlist can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
 
@@ -18306,10 +18527,11 @@
     public List<String> getSsidAllowlist() {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
-                        || isSystemUid(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || canQueryAdminPolicy(caller),
                 "SSID allowlist can only be retrieved by a device owner or "
-                        + "a profile owner on an organization-owned device or a system app.");
+                        + "a profile owner on an organization-owned device or "
+                        + "an app with the QUERY_ADMIN_POLICY permission.");
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
                     UserHandle.USER_SYSTEM);
@@ -18322,7 +18544,7 @@
     public void setSsidDenylist(List<String> ssids) {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                 "SSID denylist can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
 
@@ -18344,10 +18566,11 @@
     public List<String> getSsidDenylist() {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
-                        || isSystemUid(caller),
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || canQueryAdminPolicy(caller),
                 "SSID denylist can only be retrieved by a device owner or "
-                        + "a profile owner on an organization-owned device or a system app.");
+                        + "a profile owner on an organization-owned device or "
+                        + "an app with the QUERY_ADMIN_POLICY permission.");
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
                     UserHandle.USER_SYSTEM);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 685cf05..598f9e8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -34,6 +34,7 @@
 import android.annotation.UserIdInt;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -41,6 +42,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Binder;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.view.inputmethod.InputMethodInfo;
@@ -91,6 +93,8 @@
         List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
 
         String getActiveApexPackageNameContainingPackage(String packageName);
+
+        String getDeviceManagerRoleHolderPackageName(Context context);
     }
 
     private static final class DefaultInjector implements Injector {
@@ -104,6 +108,19 @@
         public String getActiveApexPackageNameContainingPackage(String packageName) {
             return ApexManager.getInstance().getActiveApexPackageNameContainingPackage(packageName);
         }
+
+        @Override
+        public String getDeviceManagerRoleHolderPackageName(Context context) {
+            return Binder.withCleanCallingIdentity(() -> {
+                RoleManager roleManager = context.getSystemService(RoleManager.class);
+                List<String> roleHolders =
+                        roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER);
+                if (roleHolders.isEmpty()) {
+                    return null;
+                }
+                return roleHolders.get(0);
+            });
+        }
     }
 
     @VisibleForTesting
@@ -142,9 +159,20 @@
         nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
         nonRequiredApps.removeAll(
                 getRequiredAppsMainlineModules(nonRequiredApps, provisioningAction));
+        nonRequiredApps.removeAll(getDeviceManagerRoleHolders());
         return nonRequiredApps;
     }
 
+    private Set<String> getDeviceManagerRoleHolders() {
+        HashSet<String> result = new HashSet<>();
+        String deviceManagerRoleHolderPackageName =
+                mInjector.getDeviceManagerRoleHolderPackageName(mContext);
+        if (deviceManagerRoleHolderPackageName != null) {
+            result.add(deviceManagerRoleHolderPackageName);
+        }
+        return result;
+    }
+
     /**
      * Returns a subset of {@code packageNames} whose packages are mainline modules declared as
      * required apps via their app metadata.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3584728..fe8f223 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -637,13 +637,15 @@
         }
     }
 
-    void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType) {
+    void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType,
+            boolean isAdminTestOnly) {
         synchronized (mLock) {
             if (!hasDeviceOwner()) {
                 Slog.e(TAG, "Attempting to set a device owner type when there is no device owner");
                 return;
-            } else if (isDeviceOwnerTypeSetForDeviceOwner(packageName)) {
-                Slog.e(TAG, "Device owner type for " + packageName + " has already been set");
+            } else if (!isAdminTestOnly && isDeviceOwnerTypeSetForDeviceOwner(packageName)) {
+                Slog.e(TAG, "Setting the device owner type more than once is only allowed"
+                        + " for test only admins");
                 return;
             }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 74e04ed..f4fae2f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -154,6 +154,7 @@
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.people.PeopleService;
 import com.android.server.pm.ApexManager;
+import com.android.server.pm.ApexSystemServiceInfo;
 import com.android.server.pm.CrossProfileAppsService;
 import com.android.server.pm.DataLoaderManagerService;
 import com.android.server.pm.DynamicCodeLoggingService;
@@ -176,6 +177,7 @@
 import com.android.server.powerstats.PowerStatsService;
 import com.android.server.profcollect.ProfcollectForwardingService;
 import com.android.server.recoverysystem.RecoverySystemService;
+import com.android.server.resources.ResourcesManagerService;
 import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.role.RoleServicePlatformHelper;
 import com.android.server.rotationresolver.RotationResolverManagerService;
@@ -223,8 +225,8 @@
 import java.util.Arrays;
 import java.util.Date;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Timer;
 import java.util.TreeSet;
 import java.util.concurrent.CountDownLatch;
@@ -265,8 +267,6 @@
             "/apex/com.android.os.statsd/javalib/service-statsd.jar";
     private static final String CONNECTIVITY_SERVICE_APEX_PATH =
             "/apex/com.android.tethering/javalib/service-connectivity.jar";
-    private static final String NEARBY_SERVICE_APEX_PATH =
-            "/apex/com.android.nearby/javalib/service-nearby.jar";
     private static final String STATS_COMPANION_LIFECYCLE_CLASS =
             "com.android.server.stats.StatsCompanion$Lifecycle";
     private static final String STATS_PULL_ATOM_SERVICE_CLASS =
@@ -277,8 +277,6 @@
             "com.android.server.usb.UsbService$Lifecycle";
     private static final String MIDI_SERVICE_CLASS =
             "com.android.server.midi.MidiService$Lifecycle";
-    private static final String NEARBY_SERVICE_CLASS =
-            "com.android.server.nearby.NearbyService";
     private static final String WIFI_APEX_SERVICE_JAR_PATH =
             "/apex/com.android.wifi/javalib/service-wifi.jar";
     private static final String WIFI_SERVICE_CLASS =
@@ -369,6 +367,8 @@
             "com.android.server.adb.AdbService$Lifecycle";
     private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
             "com.android.server.speech.SpeechRecognitionManagerService";
+    private static final String WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS =
+            "com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService";
     private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
             "com.android.server.appprediction.AppPredictionManagerService";
     private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
@@ -1289,6 +1289,13 @@
         mSystemServiceManager.startService(new OverlayManagerService(mSystemContext));
         t.traceEnd();
 
+        // Manages Resources packages
+        t.traceBegin("StartResourcesManagerService");
+        ResourcesManagerService resourcesService = new ResourcesManagerService(mSystemContext);
+        resourcesService.setActivityManagerService(mActivityManagerService);
+        mSystemServiceManager.startService(resourcesService);
+        t.traceEnd();
+
         t.traceBegin("StartSensorPrivacyService");
         mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext));
         t.traceEnd();
@@ -1466,7 +1473,7 @@
             // TelecomLoader hooks into classes with defined HFP logic,
             // so check for either telephony or microphone.
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
-                mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+                    mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
                 t.traceBegin("StartTelecomLoaderService");
                 mSystemServiceManager.startService(TelecomLoaderService.class);
                 t.traceEnd();
@@ -1474,7 +1481,7 @@
 
             t.traceBegin("StartTelephonyRegistry");
             telephonyRegistry = new TelephonyRegistry(
-                context, new TelephonyRegistry.ConfigurationProvider());
+                    context, new TelephonyRegistry.ConfigurationProvider());
             ServiceManager.addService("telephony.registry", telephonyRegistry);
             t.traceEnd();
 
@@ -1891,7 +1898,6 @@
             }
             t.traceEnd();
 
-
             t.traceBegin("StartIpSecService");
             try {
                 ipSecService = IpSecService.create(context);
@@ -2000,16 +2006,6 @@
             }
             t.traceEnd();
 
-            // Start Nearby Service.
-            t.traceBegin("StartNearbyService");
-            try {
-                mSystemServiceManager.startServiceFromJar(NEARBY_SERVICE_CLASS,
-                        NEARBY_SERVICE_APEX_PATH);
-            } catch (Throwable e) {
-                reportWtf("starting NearbyService", e);
-            }
-            t.traceEnd();
-
             t.traceBegin("StartConnectivityService");
             // This has to be called after NetworkManagementService, NetworkStatsService
             // and NetworkPolicyManager because ConnectivityService needs to take these
@@ -2132,6 +2128,14 @@
                 Slog.i(TAG, "Wallpaper service disabled by config");
             }
 
+            // WallpaperEffectsGeneration manager service
+            // TODO (b/135218095): Use deviceHasConfigString(context,
+            //  R.string.config_defaultWallpaperEffectsGenerationService)
+            t.traceBegin("StartWallpaperEffectsGenerationService");
+            mSystemServiceManager.startService(
+                    WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
+            t.traceEnd();
+
             t.traceBegin("StartAudioService");
             if (!isArc) {
                 mSystemServiceManager.startService(AudioService.Lifecycle.class);
@@ -3014,7 +3018,9 @@
             t.traceEnd();
             t.traceBegin("MakeTelephonyRegistryReady");
             try {
-                if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+                if (telephonyRegistryF != null) {
+                    telephonyRegistryF.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying TelephonyRegistry running", e);
             }
@@ -3079,10 +3085,12 @@
      */
     private void startApexServices(@NonNull TimingsTraceAndSlog t) {
         t.traceBegin("startApexServices");
-        Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
-        // TODO(satayev): introduce android:order for services coming the same apexes
-        for (String name : new TreeSet<>(services.keySet())) {
-            String jarPath = services.get(name);
+        // TODO(b/192880996): get the list from "android" package, once the manifest entries
+        // are migrated to system manifest.
+        List<ApexSystemServiceInfo> services = ApexManager.getInstance().getApexSystemServices();
+        for (ApexSystemServiceInfo info : services) {
+            String name = info.getName();
+            String jarPath = info.getJarPath();
             t.traceBegin("starting " + name);
             if (TextUtils.isEmpty(jarPath)) {
                 mSystemServiceManager.startService(name);
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
index 16d6241..0a9b7b1 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
@@ -32,7 +32,7 @@
     name: "test_com.android.server",
     manifest: "manifest.json",
     androidManifest: "AndroidManifest.xml",
-    java_libs: ["FakeApexSystemService"],
+    java_libs: ["FakeApexSystemServices"],
     file_contexts: ":apex.test-file_contexts",
     key: "test_com.android.server.key",
     updatable: false,
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
index eb741ca..6bec284 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
@@ -21,21 +21,29 @@
     <application android:hasCode="false" android:testOnly="true">
         <apex-system-service
             android:name="com.android.server.testing.FakeApexSystemService"
-            android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar"
-            android:minSdkVersion="30"/>
+            android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+            android:minSdkVersion="30"
+        />
+
+        <apex-system-service
+            android:name="com.android.server.testing.FakeApexSystemService2"
+            android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+            android:minSdkVersion="30"
+            android:initOrder="1"
+        />
 
         <!-- Always inactive system service, since maxSdkVersion is low -->
         <apex-system-service
-            android:name="com.android.apex.test.OldApexSystemService"
-            android:path="/apex/com.android.apex.test/javalib/fake.jar"
+            android:name="com.android.server.testing.OldApexSystemService"
+            android:path="/apex/test_com.android.server/javalib/fake.jar"
             android:minSdkVersion="1"
             android:maxSdkVersion="1"
         />
 
         <!-- Always inactive system service, since minSdkVersion is high -->
         <apex-system-service
-            android:name="com.android.apex.test.NewApexSystemService"
-            android:path="/apex/com.android.apex.test/javalib/fake.jar"
+            android:name="com.android.server.testing.NewApexSystemService"
+            android:path="/apex/test_com.android.server/javalib/fake.jar"
             android:minSdkVersion="999999"
         />
     </application>
diff --git a/services/tests/apexsystemservices/service/Android.bp b/services/tests/apexsystemservices/services/Android.bp
similarity index 94%
rename from services/tests/apexsystemservices/service/Android.bp
rename to services/tests/apexsystemservices/services/Android.bp
index 9d04f39..477ea4c 100644
--- a/services/tests/apexsystemservices/service/Android.bp
+++ b/services/tests/apexsystemservices/services/Android.bp
@@ -8,7 +8,7 @@
 }
 
 java_library {
-    name: "FakeApexSystemService",
+    name: "FakeApexSystemServices",
     srcs: ["**/*.java"],
     sdk_version: "system_server_current",
     libs: [
diff --git a/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
similarity index 100%
rename from services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java
rename to services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
diff --git a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
new file mode 100644
index 0000000..e83343b
--- /dev/null
+++ b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
@@ -0,0 +1,41 @@
+/*
+ * 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.server.testing;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.SystemService;
+
+/**
+ * A fake system service that just logs when it is started.
+ */
+public class FakeApexSystemService2 extends SystemService {
+
+    private static final String TAG = "FakeApexSystemService";
+
+    public FakeApexSystemService2(@NonNull Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        Log.d(TAG, "FakeApexSystemService2 onStart");
+    }
+}
diff --git a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
index 2b453a9..10635a1 100644
--- a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
+++ b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
@@ -37,9 +37,15 @@
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
 
+    private static final int REBOOT_TIMEOUT = 1 * 60 * 1000;
+
     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
     private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
     private final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
@@ -67,7 +73,7 @@
     }
 
     @Test
-    public void noApexSystemServerStartsWithoutApex() throws Exception {
+    public void testNoApexSystemServiceStartsWithoutApex() throws Exception {
         mPreparer.reboot();
 
         assertThat(getFakeApexSystemServiceLogcat())
@@ -75,20 +81,55 @@
     }
 
     @Test
-    public void apexSystemServerStarts() throws Exception {
+    public void testApexSystemServiceStarts() throws Exception {
         // Pre-install the apex
         String apex = "test_com.android.server.apex";
         mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
         // Reboot activates the apex
         mPreparer.reboot();
 
+        mDevice.waitForBootComplete(REBOOT_TIMEOUT);
+
         assertThat(getFakeApexSystemServiceLogcat())
                 .contains("FakeApexSystemService onStart");
     }
 
+    @Test
+    public void testInitOrder() throws Exception {
+        // Pre-install the apex
+        String apex = "test_com.android.server.apex";
+        mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
+        // Reboot activates the apex
+        mPreparer.reboot();
+
+        mDevice.waitForBootComplete(REBOOT_TIMEOUT);
+
+        assertThat(getFakeApexSystemServiceLogcat().lines()
+                .map(ApexSystemServicesTestCases::getDebugMessage)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList()))
+                .containsExactly(
+                        // Second service has a higher initOrder and must be started first
+                        "FakeApexSystemService2 onStart",
+                        "FakeApexSystemService onStart"
+                )
+                .inOrder();
+    }
+
     private String getFakeApexSystemServiceLogcat() throws DeviceNotAvailableException {
         return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "FakeApexSystemService:D",
                 "*:S");
     }
 
+    private static final Pattern DEBUG_MESSAGE =
+            Pattern.compile("(FakeApexSystemService[0-9]* onStart)");
+
+    private static String getDebugMessage(String logcatLine) {
+        return DEBUG_MESSAGE.matcher(logcatLine)
+                .results()
+                .map(m -> m.group(1))
+                .findFirst()
+                .orElse(null);
+    }
+
 }
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 9e221be..75669d5 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -52,6 +52,7 @@
         "service-blobstore",
         "service-jobscheduler",
         "service-permission.impl",
+        "service-supplementalprocess.impl",
         "services.core",
         "services.devicepolicy",
         "services.net",
diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml
new file mode 100644
index 0000000..eb15451
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportsPerformanceGameMode="false"
+    android:supportsBatteryGameMode="false"
+    android:allowGameAngleDriver="false"
+    android:allowGameDownscaling="false"
+    android:allowGameFpsOverride="false"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml
new file mode 100644
index 0000000..65b7467
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportsPerformanceGameMode="false"
+    android:supportsBatteryGameMode="false"
+    android:allowGameAngleDriver="true"
+    android:allowGameDownscaling="true"
+    android:allowGameFpsOverride="true"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
index fec9b12..e89c812 100644
--- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
@@ -359,6 +359,34 @@
                 LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder();
     }
 
+    @Test
+    public void dispatchTransientVisibilityChanged_valueUnchanged_doesNotInvokeCallback() {
+        mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+
+        assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures).hasSize(0);
+    }
+
+    @Test
+    public void dispatchTransientVisibilityChanged_valueChanged_invokesCallback() {
+        mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+
+        assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures)
+                .containsExactly(true).inOrder();
+    }
+
+    @Test
+    public void dispatchTransientVisibilityChanged_manyTimes_invokesCallbackWhenValueChanges() {
+        mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+        mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+        mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+        mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+        mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+        mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+
+        assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures)
+                .containsExactly(true, false, true).inOrder();
+    }
+
     private static class LifecycleTrackingGameSession extends GameSession {
         private enum LifecycleMethodCall {
             ON_CREATE,
@@ -368,6 +396,8 @@
         }
 
         final List<LifecycleMethodCall> mLifecycleMethodCalls = new ArrayList<>();
+        final List<Boolean> mCapturedTransientSystemBarVisibilityFromRevealGestures =
+                new ArrayList<>();
 
         @Override
         public void onCreate() {
@@ -387,5 +417,11 @@
                 mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED);
             }
         }
+
+        @Override
+        public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+                boolean visibleDueToGesture) {
+            mCapturedTransientSystemBarVisibilityFromRevealGestures.add(visibleDueToGesture);
+        }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index 28c91aa..7670953 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -101,6 +101,7 @@
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.util.Pair;
 
@@ -211,6 +212,7 @@
     @Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal;
     @Mock private MediaSessionManager mMediaSessionManager;
     @Mock private RoleManager mRoleManager;
+    @Mock private TelephonyManager mTelephonyManager;
 
     private long mCurrentTimeMillis;
 
@@ -2309,6 +2311,11 @@
         }
 
         @Override
+        TelephonyManager getTelephonyManager() {
+            return mTelephonyManager;
+        }
+
+        @Override
         AppFGSTracker getAppFGSTracker() {
             return mAppFGSTracker;
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index eed2d42..d2358a0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -23,20 +23,32 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+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.Manifest;
 import android.app.GameManager;
 import android.app.GameModeInfo;
+import android.app.GameState;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.hardware.power.Mode;
 import android.os.Bundle;
+import android.os.PowerManagerInternal;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
@@ -46,6 +58,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
 import org.junit.After;
@@ -76,6 +89,8 @@
     private TestLooper mTestLooper;
     @Mock
     private PackageManager mMockPackageManager;
+    @Mock
+    private PowerManagerInternal mMockPowerManager;
 
     // Stolen from ConnectivityServiceTest.MockContext
     class MockContext extends ContextWrapper {
@@ -149,19 +164,27 @@
         mPackageName = mMockContext.getPackageName();
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+        applicationInfo.packageName = mPackageName;
         final PackageInfo pi = new PackageInfo();
         pi.packageName = mPackageName;
         pi.applicationInfo = applicationInfo;
         final List<PackageInfo> packages = new ArrayList<>();
         packages.add(pi);
+
+        final Resources resources =
+                InstrumentationRegistry.getInstrumentation().getContext().getResources();
+        when(mMockPackageManager.getResourcesForApplication(anyString()))
+                .thenReturn(resources);
         when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
                 .thenReturn(packages);
         when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                 .thenReturn(applicationInfo);
+        LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
     }
 
     @After
     public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
         GameManagerService gameManagerService = new GameManagerService(mMockContext);
         gameManagerService.disableCompatScale(mPackageName);
         if (mMockingSession != null) {
@@ -310,6 +333,46 @@
                 .thenReturn(applicationInfo);
     }
 
+    private void mockInterventionsEnabledFromXml() throws Exception {
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+        Bundle metaDataBundle = new Bundle();
+        final int resId = 123;
+        metaDataBundle.putInt(
+                GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
+        applicationInfo.metaData = metaDataBundle;
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+        seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
+                "res/xml/gama_manager_service_metadata_config_enabled.xml");
+    }
+
+    private void mockInterventionsDisabledFromXml() throws Exception {
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+        Bundle metaDataBundle = new Bundle();
+        final int resId = 123;
+        metaDataBundle.putInt(
+                GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
+        applicationInfo.metaData = metaDataBundle;
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+        seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
+                "res/xml/gama_manager_service_metadata_config_disabled.xml");
+    }
+
+
+    private void seedGameManagerServiceMetaDataFromFile(String packageName, int resId,
+            String fileName)
+            throws Exception {
+        AssetManager assetManager =
+                InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+        XmlResourceParser xmlResourceParser =
+                assetManager.openXmlResourceParser(fileName);
+        when(mMockPackageManager.getXml(eq(packageName), eq(resId), any()))
+                .thenReturn(xmlResourceParser);
+    }
+
     /**
      * By default game mode is not supported.
      */
@@ -499,8 +562,8 @@
                 gameManagerService.getConfig(mPackageName);
         assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
 
-        // Validate GameManagerService.getAngleEnabled() returns the correct value.
-        assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
+        // Validate GameManagerService.isAngleEnabled() returns the correct value.
+        assertEquals(gameManagerService.isAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
     }
 
     private void checkFps(GameManagerService gameManagerService, int gameMode, int fps) {
@@ -511,7 +574,7 @@
         }
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName);
-        assertEquals(config.getGameModeConfiguration(gameMode).getFps(), fps);
+        assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
     }
 
     /**
@@ -893,6 +956,36 @@
     }
 
     @Test
+    public void testGameModeConfigAllowFpsTrue() throws Exception {
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledFromXml();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        GameManagerService.GamePackageConfiguration config =
+                gameManagerService.getConfig(mPackageName);
+        assertEquals(90,
+                config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
+        assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
+    }
+
+    @Test
+    public void testGameModeConfigAllowFpsFalse() throws Exception {
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        mockInterventionsDisabledFromXml();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        GameManagerService.GamePackageConfiguration config =
+                gameManagerService.getConfig(mPackageName);
+        assertEquals(0,
+                config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
+        assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
+    }
+
+    @Test
     public void testInterventionFps() throws Exception {
         mockDeviceConfigAll();
         mockModifyGameModeGranted();
@@ -1052,4 +1145,41 @@
         assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode());
         assertEquals(0, gameModeInfo.getAvailableGameModes().length);
     }
+
+    @Test
+    public void testGameStateLoadingRequiresPerformanceMode() {
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        GameState gameState = new GameState(true, GameState.MODE_NONE);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+    }
+
+    private void setGameState(boolean isLoading) {
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        gameManagerService.setGameMode(
+                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        GameState gameState = new GameState(isLoading, GameState.MODE_NONE);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading);
+    }
+
+    @Test
+    public void testSetGameStateLoading() {
+        setGameState(true);
+    }
+
+    @Test
+    public void testSetGameStateNotLoading() {
+        setGameState(false);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index d5e4710..08de62b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -26,13 +26,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+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.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
@@ -70,6 +71,7 @@
 import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
 import com.android.internal.util.Preconditions;
 import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
 import com.android.server.wm.WindowManagerService;
 
 import org.junit.After;
@@ -83,6 +85,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Objects;
 
 
 /**
@@ -109,6 +112,7 @@
     private static final ComponentName GAME_B_MAIN_ACTIVITY =
             new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity");
 
+
     private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
 
     private MockitoSession mMockingSession;
@@ -121,13 +125,14 @@
     private WindowManagerInternal mMockWindowManagerInternal;
     @Mock
     private IActivityManager mMockActivityManager;
-    private FakeContext mFakeContext;
+    private MockContext mMockContext;
     private FakeGameClassifier mFakeGameClassifier;
     private FakeGameService mFakeGameService;
     private FakeServiceConnector<IGameService> mFakeGameServiceConnector;
     private FakeGameSessionService mFakeGameSessionService;
     private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector;
     private ArrayList<ITaskStackListener> mTaskStackListeners;
+    private ArrayList<TaskSystemBarsListener> mTaskSystemBarsListeners;
     private ArrayList<RunningTaskInfo> mRunningTaskInfos;
 
     @Mock
@@ -140,7 +145,7 @@
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
-        mFakeContext = new FakeContext(InstrumentationRegistry.getInstrumentation().getContext());
+        mMockContext = new MockContext(InstrumentationRegistry.getInstrumentation().getContext());
 
         mFakeGameClassifier = new FakeGameClassifier();
         mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE);
@@ -156,20 +161,30 @@
             mTaskStackListeners.add(invocation.getArgument(0));
             return null;
         }).when(mMockActivityTaskManager).registerTaskStackListener(any());
-
-        mRunningTaskInfos = new ArrayList<>();
-        when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn(
-                mRunningTaskInfos);
-
         doAnswer(invocation -> {
             mTaskStackListeners.remove(invocation.getArgument(0));
             return null;
         }).when(mMockActivityTaskManager).unregisterTaskStackListener(any());
 
+        mTaskSystemBarsListeners = new ArrayList<>();
+        doAnswer(invocation -> {
+            mTaskSystemBarsListeners.add(invocation.getArgument(0));
+            return null;
+        }).when(mMockWindowManagerInternal).registerTaskSystemBarsListener(any());
+        doAnswer(invocation -> {
+            mTaskSystemBarsListeners.remove(invocation.getArgument(0));
+            return null;
+        }).when(mMockWindowManagerInternal).unregisterTaskSystemBarsListener(any());
+
+        mRunningTaskInfos = new ArrayList<>();
+        when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn(
+                mRunningTaskInfos);
+
+
         mGameServiceProviderInstance = new GameServiceProviderInstanceImpl(
                 new UserHandle(USER_ID),
                 ConcurrentUtils.DIRECT_EXECUTOR,
-                mFakeContext,
+                mMockContext,
                 mFakeGameClassifier,
                 mMockActivityManager,
                 mMockActivityTaskManager,
@@ -301,6 +316,7 @@
             throws Exception {
         mGameServiceProviderInstance.start();
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -322,6 +338,7 @@
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation =
@@ -336,6 +353,7 @@
         mGameServiceProviderInstance.start();
         dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -345,6 +363,7 @@
     public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -362,7 +381,9 @@
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10,
@@ -376,6 +397,7 @@
     public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -384,13 +406,67 @@
                 .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
 
         verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10));
-        verifyNoMoreInteractions(mMockWindowManagerInternal);
+    }
+
+    @Test
+    public void taskSystemBarsListenerChanged_noAssociatedGameSession_doesNothing() {
+        mGameServiceProviderInstance.start();
+
+        dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+            taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+                    10,
+                    /* areVisible= */ false,
+                    /* wereRevealedFromSwipeOnSystemBar= */ false);
+        });
+    }
+
+    @Test
+    public void systemBarsTransientShownDueToGesture_hasGameSession_propagatesToGameSession() {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+            taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+                    10,
+                    /* areVisible= */ true,
+                    /* wereRevealedFromSwipeOnSystemBar= */ true);
+        });
+
+        assertThat(gameSession10.mAreTransientSystemBarsVisibleFromRevealGesture).isTrue();
+    }
+
+    @Test
+    public void systemBarsTransientShownButNotGesture_hasGameSession_notPropagatedToGameSession() {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+            taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+                    10,
+                    /* areVisible= */ true,
+                    /* wereRevealedFromSwipeOnSystemBar= */ false);
+        });
+
+        assertThat(gameSession10.mAreTransientSystemBarsVisibleFromRevealGesture).isFalse();
     }
 
     @Test
     public void gameTaskFocused_propagatedToGameSession() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -416,6 +492,7 @@
 
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -432,6 +509,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         dispatchTaskRemoved(10);
@@ -449,6 +527,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -466,6 +545,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -477,7 +557,6 @@
 
         verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10));
         verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10));
-        verifyNoMoreInteractions(mMockWindowManagerInternal);
     }
 
     @Test
@@ -486,6 +565,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -513,6 +593,7 @@
         startTask(10, GAME_A_MAIN_ACTIVITY);
         startTask(11, GAME_A_MAIN_ACTIVITY);
 
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -530,6 +611,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -557,6 +639,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -586,6 +669,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -619,10 +703,19 @@
     }
 
     @Test
+    public void createGameSession_failurePermissionDenied() throws Exception {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
+        assertThrows(SecurityException.class, () -> mFakeGameService.requestCreateGameSession(10));
+    }
+
+    @Test
     public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception {
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -650,6 +743,7 @@
     public void takeScreenshot_failureNoBitmapCaptured() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         IGameSessionController gameSessionController = getOnlyElement(
@@ -669,6 +763,7 @@
 
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         IGameSessionController gameSessionController = getOnlyElement(
@@ -683,6 +778,7 @@
 
     @Test
     public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception {
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE")
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE))
@@ -691,6 +787,7 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -710,14 +807,16 @@
                 .mGameSessionController.restartGame(10);
 
         verify(mMockActivityManager).forceStopPackage(GAME_A_PACKAGE, UserHandle.USER_CURRENT);
-        assertThat(mFakeContext.getLastStartedIntent()).isEqualTo(launchIntent);
+        assertThat(mMockContext.getLastStartedIntent()).isEqualTo(launchIntent);
     }
 
     @Test
     public void restartGame_taskIdNotAssociatedWithGame_noOp() throws Exception {
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -730,7 +829,20 @@
                 .mGameSessionController.restartGame(11);
 
         verifyZeroInteractions(mMockActivityManager);
-        assertThat(mFakeContext.getLastStartedIntent()).isNull();
+        assertThat(mMockContext.getLastStartedIntent()).isNull();
+    }
+
+    @Test
+    public void restartGame_failurePermissionDenied() throws Exception {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+        IGameSessionController gameSessionController = Objects.requireNonNull(getOnlyElement(
+                mFakeGameSessionService.getCapturedCreateInvocations())).mGameSessionController;
+        mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
+        assertThrows(SecurityException.class,
+                () -> gameSessionController.restartGame(10));
     }
 
     private void startTask(int taskId, ComponentName componentName) {
@@ -774,6 +886,21 @@
         }
     }
 
+    private void mockPermissionGranted(String permission) {
+        mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void mockPermissionDenied(String permission) {
+        mMockContext.setPermission(permission, PackageManager.PERMISSION_DENIED);
+    }
+
+    private void dispatchTaskSystemBarsEvent(
+            ThrowingConsumer<TaskSystemBarsListener> taskSystemBarsListenerConsumer) {
+        for (TaskSystemBarsListener listener : mTaskSystemBarsListeners) {
+            taskSystemBarsListenerConsumer.accept(listener);
+        }
+    }
+
     static final class FakeGameService extends IGameService.Stub {
         private IGameServiceController mGameServiceController;
 
@@ -888,6 +1015,7 @@
     private static class FakeGameSession extends IGameSession.Stub {
         boolean mIsDestroyed = false;
         boolean mIsFocused = false;
+        boolean mAreTransientSystemBarsVisibleFromRevealGesture = false;
 
         @Override
         public void onDestroyed() {
@@ -898,15 +1026,35 @@
         public void onTaskFocusChanged(boolean focused) {
             mIsFocused = focused;
         }
+
+        @Override
+        public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean areVisible) {
+            mAreTransientSystemBarsVisibleFromRevealGesture = areVisible;
+        }
     }
 
-    private final class FakeContext extends ContextWrapper {
+    private final class MockContext extends ContextWrapper {
         private Intent mLastStartedIntent;
+        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
 
-        FakeContext(Context base) {
+        MockContext(Context base) {
             super(base);
         }
 
+        /**
+         * Mock checks for the specified permission, and have them behave as per {@code granted}.
+         *
+         * <p>Passing null reverts to default behavior, which does a real permission check on the
+         * test package.
+         *
+         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+         *                {@link PackageManager#PERMISSION_DENIED}.
+         */
+        public void setPermission(String permission, Integer granted) {
+            mMockedPermissions.put(permission, granted);
+        }
+
         @Override
         public PackageManager getPackageManager() {
             return mMockPackageManager;
@@ -919,12 +1067,19 @@
 
         @Override
         public void enforceCallingPermission(String permission, @Nullable String message) {
-            // Do nothing.
+            final Integer granted = mMockedPermissions.get(permission);
+            if (granted == null) {
+                super.enforceCallingOrSelfPermission(permission, message);
+                return;
+            }
+
+            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+                throw new SecurityException("[Test] permission denied: " + permission);
+            }
         }
 
         Intent getLastStartedIntent() {
             return mLastStartedIntent;
         }
     }
-
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index bdeb2b4..f9bdad6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -204,6 +204,49 @@
                 jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
     }
 
+    @Test
+    public void testGetMinJobExecutionGuaranteeMs() {
+        JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                createJobInfo(1).setExpedited(true));
+        JobStatus ejHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                createJobInfo(2).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH));
+        JobStatus ejMaxDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                createJobInfo(3).setExpedited(true));
+        JobStatus ejHighDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                createJobInfo(4).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH));
+        JobStatus jobHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
+        JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                createJobInfo(6));
+
+        spyOn(ejMax);
+        spyOn(ejHigh);
+        spyOn(ejMaxDowngraded);
+        spyOn(ejHighDowngraded);
+        spyOn(jobHigh);
+        spyOn(jobDef);
+
+        when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
+        when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true);
+        when(ejMaxDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
+        when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
+        when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
+        when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
+
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(ejMax));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(ejHigh));
+        assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(ejMaxDowngraded));
+        assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(ejHighDowngraded));
+        assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobHigh));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobDef));
+    }
+
     /**
      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
      * with the correct delay and deadline constraints if the periodic job is scheduled with the
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 153ce17..9d6793c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -30,6 +30,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -536,6 +537,7 @@
 
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
@@ -595,6 +597,7 @@
         assertNotNull(mQuotaController.getEJTimingSessions(10, "com.android.test"));
 
         ExecutionStats expectedStats = new ExecutionStats();
+        expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
@@ -638,11 +641,13 @@
         ExecutionStats expectedStats = new ExecutionStats();
         ExecutionStats inputStats = new ExecutionStats();
 
+        inputStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
         inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
         inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
         // Invalid time is now +24 hours since there are no sessions at all for the app.
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
         synchronized (mQuotaController.mLock) {
             mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
         }
@@ -827,6 +832,8 @@
 
         ExecutionStats expectedStats = new ExecutionStats();
         ExecutionStats inputStats = new ExecutionStats();
+        inputStats.allowedTimePerPeriodMs = expectedStats.allowedTimePerPeriodMs =
+                10 * MINUTE_IN_MILLIS;
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
         inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
         inputStats.sessionCountLimit = expectedStats.sessionCountLimit =
@@ -924,6 +931,7 @@
         ExecutionStats expectedStats = new ExecutionStats();
 
         // Active
+        expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
@@ -1006,6 +1014,7 @@
         ExecutionStats expectedStats = new ExecutionStats();
 
         // Active
+        expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
@@ -1242,6 +1251,7 @@
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 6 * HOUR_IN_MILLIS);
 
         ExecutionStats expectedStats = new ExecutionStats();
+        expectedStats.allowedTimePerPeriodMs = originalStatsActive.allowedTimePerPeriodMs;
         expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
         expectedStats.jobCountLimit = originalStatsActive.jobCountLimit;
         expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit;
@@ -1261,6 +1271,7 @@
         assertTrue(originalStatsActive == newStatsActive);
         assertEquals(expectedStats, newStatsActive);
 
+        expectedStats.allowedTimePerPeriodMs = originalStatsWorking.allowedTimePerPeriodMs;
         expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
         expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit;
         expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit;
@@ -1277,6 +1288,7 @@
         assertTrue(originalStatsWorking == newStatsWorking);
         assertNotEquals(expectedStats, newStatsWorking);
 
+        expectedStats.allowedTimePerPeriodMs = originalStatsFrequent.allowedTimePerPeriodMs;
         expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
         expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit;
         expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit;
@@ -1293,6 +1305,7 @@
         assertTrue(originalStatsFrequent == newStatsFrequent);
         assertNotEquals(expectedStats, newStatsFrequent);
 
+        expectedStats.allowedTimePerPeriodMs = originalStatsRare.allowedTimePerPeriodMs;
         expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
         expectedStats.jobCountLimit = originalStatsRare.jobCountLimit;
         expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit;
@@ -1354,7 +1367,8 @@
     @Test
     public void testGetMaxJobExecutionTimeLocked_Regular_Active() {
         JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked_Regular_Active", 0);
-        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+                10 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 10 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 2 * HOUR_IN_MILLIS);
         setDischarging();
@@ -2886,11 +2900,12 @@
     public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() {
         // Set rate limiting period different from allowed time to confirm code sets based on
         // the former.
-        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS);
+        final int standbyBucket = WORKING_INDEX;
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+                10 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 5 * MINUTE_IN_MILLIS);
 
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        final int standbyBucket = WORKING_INDEX;
 
         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1);
         setStandbyBucket(standbyBucket, jobStatus);
@@ -2953,8 +2968,8 @@
     @Test
     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
         // Make sure any new value is used correctly.
-        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS,
-                mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+                mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2);
 
         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -2977,8 +2992,8 @@
         // Make sure any new value is used correctly.
         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS,
                 mQcConstants.IN_QUOTA_BUFFER_MS * 2);
-        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS,
-                mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+                mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2);
         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS,
                 mQcConstants.MAX_EXECUTION_TIME_MS / 2);
 
@@ -3002,7 +3017,8 @@
         // Working set window size is 2 hours.
         final int standbyBucket = WORKING_INDEX;
         final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
-        final long remainingTimeMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
+        final long remainingTimeMs =
+                mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS - contributionMs;
 
         // Session straddles edge of bucket window. Only the contribution should be counted towards
         // the quota.
@@ -3062,16 +3078,28 @@
 
     @Test
     public void testConstantsUpdating_ValidValues() {
-        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+                8 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+                5 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+                7 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+                2 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 4 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+                11 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS);
         setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, .7f);
         setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .2f);
+        setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 99 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 60 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 120 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3 * HOUR_IN_MILLIS);
+        setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, 6000);
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, 5000);
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 4000);
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 3000);
@@ -3079,6 +3107,7 @@
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, 2000);
         setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * MINUTE_IN_MILLIS);
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 500);
+        setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, 600);
         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 500);
         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 400);
         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 300);
@@ -3088,6 +3117,7 @@
         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
                 10 * SECOND_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 7 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 3 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 2 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 90 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS);
@@ -3104,10 +3134,23 @@
                 84 * SECOND_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS);
 
-        assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
+        assertEquals(8 * MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
+        assertEquals(5 * MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
+        assertEquals(7 * MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
+        assertEquals(2 * MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
+        assertEquals(4 * MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
+        assertEquals(11 * MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
         assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
         assertEquals(.7f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
         assertEquals(.2f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
+        assertEquals(99 * MINUTE_IN_MILLIS,
+                mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
         assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
         assertEquals(45 * MINUTE_IN_MILLIS,
@@ -3118,12 +3161,14 @@
         assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
         assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow());
+        assertEquals(6000, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]);
         assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
         assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
         assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
         assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
         assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]);
         assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
+        assertEquals(600, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]);
         assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
         assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
         assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
@@ -3132,6 +3177,7 @@
         assertEquals(10 * SECOND_IN_MILLIS,
                 mQuotaController.getTimingSessionCoalescingDurationMs());
         assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs());
+        assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
         assertEquals(2 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
         assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
         assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
@@ -3151,16 +3197,24 @@
     @Test
     public void testConstantsUpdating_InvalidValues() {
         // Test negatives/too low.
-        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, -MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, -MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, -MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, -MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, -MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, -MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+                -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, -.1f);
         setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, -.01f);
+        setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, -MINUTE_IN_MILLIS);
+        setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, -1);
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, -1);
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 1);
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 1);
@@ -3168,6 +3222,7 @@
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, -1);
         setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * SECOND_IN_MILLIS);
         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 0);
+        setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, -1);
         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, -1);
         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 0);
         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, -3);
@@ -3176,6 +3231,7 @@
         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 0);
         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, -1);
+        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1);
@@ -3191,10 +3247,19 @@
         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1);
         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1);
 
-        assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
+        assertEquals(MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
+        assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
+        assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
+        assertEquals(MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
+        assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
+        assertEquals(MINUTE_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
         assertEquals(0, mQuotaController.getInQuotaBufferMs());
         assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
         assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
+        assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -3203,12 +3268,14 @@
         assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
         assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
         assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow());
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]);
         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]);
         assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]);
         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
@@ -3216,6 +3283,7 @@
         assertEquals(0, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]);
         assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs());
         assertEquals(0, mQuotaController.getMinQuotaCheckDelayMs());
+        assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
         assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
@@ -3233,17 +3301,37 @@
 
         // Invalid configurations.
         // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
-        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 2 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+                10 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+                10 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+                10 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+                2 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 10 * MINUTE_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+                10 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 5 * MINUTE_IN_MILLIS);
 
         assertTrue(mQuotaController.getInQuotaBufferMs()
-                <= mQuotaController.getAllowedTimePerPeriodMs());
+                <= mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
 
         // Test larger than a day. Controller should cap at one day.
-        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 25 * HOUR_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+                25 * HOUR_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+                25 * HOUR_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+                25 * HOUR_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 25 * HOUR_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+                25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, 1f);
         setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .95f);
+        setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
@@ -3254,6 +3342,7 @@
         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
                 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 25 * HOUR_IN_MILLIS);
+        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
@@ -3269,10 +3358,21 @@
         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
 
-        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
+        assertEquals(24 * HOUR_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
+        assertEquals(24 * HOUR_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
+        assertEquals(24 * HOUR_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
+        assertEquals(24 * HOUR_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
+        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
+        assertEquals(24 * HOUR_IN_MILLIS,
+                mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
         assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
         assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
         assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
+        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -3284,6 +3384,7 @@
         assertEquals(15 * MINUTE_IN_MILLIS,
                 mQuotaController.getTimingSessionCoalescingDurationMs());
         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs());
+        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 6ae0031..4ec1641 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -69,6 +69,7 @@
 import com.android.server.pm.pkg.parsing.ParsingPackage
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.supplementalprocess.SupplementalProcessManagerLocal
 import com.android.server.testutils.TestHandler
 import com.android.server.testutils.mock
 import com.android.server.testutils.nullable
@@ -335,6 +336,7 @@
         stageServicesExtensionScan()
         stageSystemSharedLibraryScan()
         stagePermissionsControllerScan()
+        stageSupplementalProcessScan()
         stageInstantAppResolverScan()
     }
 
@@ -569,6 +571,22 @@
     }
 
     @Throws(Exception::class)
+    private fun stageSupplementalProcessScan() {
+        stageScanNewPackage("com.android.supplemental.process",
+                1L, systemPartitions[0].privAppFolder,
+                withPackage = { pkg: PackageImpl ->
+                    val applicationInfo: ApplicationInfo = createBasicApplicationInfo(pkg)
+                    mockQueryServices(SupplementalProcessManagerLocal.SERVICE_INTERFACE,
+                            createBasicServiceInfo(
+                                    pkg, applicationInfo, "SupplementalProcessService"))
+                    pkg
+                },
+                withSetting = { setting: PackageSettingBuilder ->
+                    setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
+                })
+    }
+
+    @Throws(Exception::class)
     private fun stageSystemSharedLibraryScan() {
         stageScanNewPackage("android.ext.shared",
                 1L, systemPartitions[0].appFolder,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index bdfdf77..64657a9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -50,7 +50,7 @@
 import android.util.SparseArray;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
 
@@ -101,12 +101,12 @@
         mMockitoSession = ExtendedMockito.mockitoSession()
                     .strictness(Strictness.LENIENT)
                     .mockStatic(SystemProperties.class)
-                    .mockStatic(PackageHelper.class)
+                    .mockStatic(InstallLocationUtils.class)
                     .startMocking();
 
         when(mStorageManager.supportsCheckpoint()).thenReturn(true);
         when(mStorageManager.needsCheckpoint()).thenReturn(true);
-        when(PackageHelper.getStorageManager()).thenReturn(mStorageManager);
+        when(InstallLocationUtils.getStorageManager()).thenReturn(mStorageManager);
 
         when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true");
         when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
new file mode 100644
index 0000000..c325778
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.R;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ExecutionException;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DockObserverTest {
+
+    @Rule
+    public TestableContext mContext =
+            new TestableContext(ApplicationProvider.getApplicationContext(), null);
+
+    private final BroadcastInterceptingContext mInterceptingContext =
+            new BroadcastInterceptingContext(mContext);
+
+    BroadcastInterceptingContext.FutureIntent updateExtconDockState(DockObserver observer,
+            String extconDockState) {
+        BroadcastInterceptingContext.FutureIntent futureIntent =
+                mInterceptingContext.nextBroadcastIntent(Intent.ACTION_DOCK_EVENT);
+        observer.setDockStateFromProviderForTesting(
+                DockObserver.ExtconStateProvider.fromString(extconDockState));
+        TestableLooper.get(this).processAllMessages();
+        return futureIntent;
+    }
+
+    DockObserver observerWithMappingConfig(String[] configEntries) {
+        mContext.getOrCreateTestableResources().addOverride(
+                R.array.config_dockExtconStateMapping,
+                configEntries);
+        return new DockObserver(mInterceptingContext);
+    }
+
+    void assertDockEventIntentWithExtraThenUndock(DockObserver observer, String extconDockState,
+            int expectedExtra) throws ExecutionException, InterruptedException {
+        assertThat(updateExtconDockState(observer, extconDockState)
+                .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+                .isEqualTo(expectedExtra);
+        assertThat(updateExtconDockState(observer, "DOCK=0")
+                .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+                .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
+    }
+
+    @Before
+    public void setUp() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    @Test
+    public void testDockIntentBroadcast_onlyAfterBootReady()
+            throws ExecutionException, InterruptedException {
+        DockObserver observer = new DockObserver(mInterceptingContext);
+        BroadcastInterceptingContext.FutureIntent futureIntent =
+                updateExtconDockState(observer, "DOCK=1");
+        updateExtconDockState(observer, "DOCK=1").assertNotReceived();
+        // Last boot phase reached
+        observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+        TestableLooper.get(this).processAllMessages();
+        assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+                .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+    }
+
+    @Test
+    public void testDockIntentBroadcast_customConfigResource()
+            throws ExecutionException, InterruptedException {
+        DockObserver observer = observerWithMappingConfig(
+                new String[] {"2,KEY1=1,KEY2=2", "3,KEY3=3"});
+        observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+        // Mapping should not match
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1",
+                Intent.EXTRA_DOCK_STATE_DESK);
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY1=1",
+                Intent.EXTRA_DOCK_STATE_DESK);
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2",
+                Intent.EXTRA_DOCK_STATE_DESK);
+
+        // 1st mapping now matches
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2\nKEY1=1",
+                Intent.EXTRA_DOCK_STATE_CAR);
+
+        // 2nd mapping now matches
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY3=3",
+                Intent.EXTRA_DOCK_STATE_LE_DESK);
+    }
+
+    @Test
+    public void testDockIntentBroadcast_customConfigResourceWithWildcard()
+            throws ExecutionException, InterruptedException {
+        DockObserver observer = observerWithMappingConfig(new String[] {
+                "2,KEY2=2",
+                "3,KEY3=3",
+                "4"
+        });
+        observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+        assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
+                Intent.EXTRA_DOCK_STATE_HE_DESK);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index 9ee1205..3890d4d 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -24,14 +24,20 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.fail;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.attention.AttentionManagerInternal.ProximityCallbackInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.IBinder;
@@ -42,6 +48,7 @@
 import android.provider.DeviceConfig;
 import android.service.attention.IAttentionCallback;
 import android.service.attention.IAttentionService;
+import android.service.attention.IProximityCallback;
 
 import androidx.test.filters.SmallTest;
 
@@ -49,6 +56,7 @@
 import com.android.server.attention.AttentionManagerService.AttentionCheckCache;
 import com.android.server.attention.AttentionManagerService.AttentionCheckCacheBuffer;
 import com.android.server.attention.AttentionManagerService.AttentionHandler;
+import com.android.server.attention.AttentionManagerService.ProximityUpdate;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -59,10 +67,13 @@
 /**
  * Tests for {@link com.android.server.attention.AttentionManagerService}
  */
+@SuppressWarnings("GuardedBy")
 @SmallTest
 public class AttentionManagerServiceTest {
+    private static final double PROXIMITY_SUCCESS_STATE = 1.0;
     private AttentionManagerService mSpyAttentionManager;
     private final int mTimeout = 1000;
+    private final Object mLock = new Object();
     @Mock
     private AttentionCallbackInternal mMockAttentionCallbackInternal;
     @Mock
@@ -73,6 +84,8 @@
     private IThermalService mMockIThermalService;
     @Mock
     Context mContext;
+    @Mock
+    private ProximityCallbackInternal mMockProximityCallbackInternal;
 
     @Before
     public void setUp() throws RemoteException {
@@ -84,7 +97,6 @@
         doReturn(true).when(mMockIPowerManager).isInteractive();
         mPowerManager = new PowerManager(mContext, mMockIPowerManager, mMockIThermalService, null);
 
-        Object mLock = new Object();
         // setup a spy on attention manager
         AttentionManagerService attentionManager = new AttentionManagerService(
                 mContext,
@@ -100,6 +112,119 @@
                 mSpyAttentionManager);
         mSpyAttentionManager.mCurrentAttentionCheck = attentionCheck;
         mSpyAttentionManager.mService = new MockIAttentionService();
+        doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();
+    }
+
+    @Test
+    public void testRegisterProximityUpdates_returnFalseWhenServiceDisabled() {
+        mSpyAttentionManager.mIsServiceEnabled = false;
+
+        assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+                .isFalse();
+    }
+
+    @Test
+    public void testRegisterProximityUpdates_returnFalseWhenServiceUnavailable() {
+        mSpyAttentionManager.mIsServiceEnabled = true;
+        doReturn(false).when(mSpyAttentionManager).isServiceAvailable();
+
+        assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+                .isFalse();
+    }
+
+    @Test
+    public void testRegisterProximityUpdates_returnFalseWhenPowerManagerNotInteract()
+            throws RemoteException {
+        mSpyAttentionManager.mIsServiceEnabled = true;
+        doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+        doReturn(false).when(mMockIPowerManager).isInteractive();
+
+        assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+                .isFalse();
+    }
+
+    @Test
+    public void testRegisterProximityUpdates_callOnSuccess() throws RemoteException {
+        mSpyAttentionManager.mIsServiceEnabled = true;
+        doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+        doReturn(true).when(mMockIPowerManager).isInteractive();
+
+        assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+                .isTrue();
+        verify(mMockProximityCallbackInternal, times(1))
+                .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+    }
+
+    @Test
+    public void testRegisterProximityUpdates_callOnSuccessTwiceInARow() throws RemoteException {
+        mSpyAttentionManager.mIsServiceEnabled = true;
+        doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+        doReturn(true).when(mMockIPowerManager).isInteractive();
+
+        assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+                .isTrue();
+
+        ProximityUpdate prevProximityUpdate = mSpyAttentionManager.mCurrentProximityUpdate;
+        assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+                .isTrue();
+        assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isEqualTo(prevProximityUpdate);
+        verify(mMockProximityCallbackInternal, times(1))
+                .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+    }
+
+    @Test
+    public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() {
+        mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+        verifyZeroInteractions(mMockProximityCallbackInternal);
+    }
+
+    @Test
+    public void testUnregisterProximityUpdates_noCrashWhenCallbackMismatched()
+            throws RemoteException {
+        mSpyAttentionManager.mIsServiceEnabled = true;
+        doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+        doReturn(true).when(mMockIPowerManager).isInteractive();
+        mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+        verify(mMockProximityCallbackInternal, times(1))
+                .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+
+        ProximityCallbackInternal mismatchedCallback = new ProximityCallbackInternal() {
+            @Override
+            public void onProximityUpdate(double distance) {
+                fail("Callback shouldn't have responded.");
+            }
+        };
+        mSpyAttentionManager.onStopProximityUpdates(mismatchedCallback);
+
+        verifyNoMoreInteractions(mMockProximityCallbackInternal);
+    }
+
+    @Test
+    public void testUnregisterProximityUpdates_cancelRegistrationWhenMatched()
+            throws RemoteException {
+        mSpyAttentionManager.mIsServiceEnabled = true;
+        doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+        doReturn(true).when(mMockIPowerManager).isInteractive();
+        mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+        mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+
+        assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isNull();
+    }
+
+    @Test
+    public void testUnregisterProximityUpdates_noCrashWhenTwiceInARow() throws RemoteException {
+        // Attention Service registers proximity updates.
+        mSpyAttentionManager.mIsServiceEnabled = true;
+        doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+        doReturn(true).when(mMockIPowerManager).isInteractive();
+        mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+        verify(mMockProximityCallbackInternal, times(1))
+                .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+
+        // Attention Service unregisters the proximity update twice in a row.
+        mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+        mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+        verifyNoMoreInteractions(mMockProximityCallbackInternal);
     }
 
     @Test
@@ -127,7 +252,6 @@
         mSpyAttentionManager.mIsServiceEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(true).when(mMockIPowerManager).isInteractive();
-        doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();
         mSpyAttentionManager.mCurrentAttentionCheck = null;
 
         AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class);
@@ -213,6 +337,13 @@
         public void cancelAttentionCheck(IAttentionCallback callback) {
         }
 
+        public void onStartProximityUpdates(IProximityCallback callback) throws RemoteException {
+            callback.onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+        }
+
+        public void onStopProximityUpdates() throws RemoteException {
+        }
+
         public IBinder asBinder() {
             return null;
         }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
new file mode 100644
index 0000000..5746f6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.log;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.StatusBarManager;
+import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
+import com.android.internal.statusbar.IStatusBarService;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class BiometricContextProviderTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IStatusBarService mStatusBarService;
+    @Mock
+    private ISessionListener mSessionListener;
+    @Mock
+    private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+
+    private OperationContext mOpContext = new OperationContext();
+    private IBiometricContextListener mListener;
+    private BiometricContextProvider mProvider;
+
+    @Before
+    public void setup() throws RemoteException {
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mProvider = new BiometricContextProvider(mAmbientDisplayConfiguration, mStatusBarService,
+                null /* handler */);
+        ArgumentCaptor<IBiometricContextListener> captor =
+                ArgumentCaptor.forClass(IBiometricContextListener.class);
+        verify(mStatusBarService).setBiometicContextListener(captor.capture());
+        mListener = captor.getValue();
+        ArgumentCaptor<ISessionListener> sessionCaptor =
+                ArgumentCaptor.forClass(ISessionListener.class);
+        verify(mStatusBarService).registerSessionListener(anyInt(), sessionCaptor.capture());
+        mSessionListener = sessionCaptor.getValue();
+    }
+
+    @Test
+    public void testIsAoD() throws RemoteException {
+        mListener.onDozeChanged(true);
+        assertThat(mProvider.isAoD()).isTrue();
+        mListener.onDozeChanged(false);
+        assertThat(mProvider.isAoD()).isFalse();
+
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false);
+        mListener.onDozeChanged(true);
+        assertThat(mProvider.isAoD()).isFalse();
+        mListener.onDozeChanged(false);
+        assertThat(mProvider.isAoD()).isFalse();
+    }
+
+    @Test
+    public void testSubscribesToAoD() throws RemoteException {
+        final List<Boolean> expected = ImmutableList.of(true, false, true, true, false);
+        final List<Boolean> actual = new ArrayList<>();
+
+        mProvider.subscribe(mOpContext, ctx -> {
+            assertThat(ctx).isSameInstanceAs(mOpContext);
+            actual.add(ctx.isAoD);
+        });
+
+        for (boolean v : expected) {
+            mListener.onDozeChanged(v);
+        }
+
+        assertThat(actual).containsExactlyElementsIn(expected).inOrder();
+    }
+
+    @Test
+    public void testUnsubscribes() throws RemoteException {
+        final Consumer<OperationContext> emptyConsumer = mock(Consumer.class);
+        mProvider.subscribe(mOpContext, emptyConsumer);
+        mProvider.unsubscribe(mOpContext);
+
+        mListener.onDozeChanged(true);
+
+        final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
+        mProvider.subscribe(mOpContext, nonEmptyConsumer);
+        mListener.onDozeChanged(false);
+        mProvider.unsubscribe(mOpContext);
+        mListener.onDozeChanged(true);
+
+        verify(emptyConsumer, never()).accept(any());
+        verify(nonEmptyConsumer).accept(same(mOpContext));
+    }
+
+    @Test
+    public void testSessionId() throws RemoteException {
+        final int keyguardSessionId = 10;
+        final int bpSessionId = 20;
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+        assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+
+        mSessionListener.onSessionStarted(StatusBarManager.SESSION_KEYGUARD,
+                InstanceId.fakeInstanceId(keyguardSessionId));
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+        assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId);
+
+        mSessionListener.onSessionStarted(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+                InstanceId.fakeInstanceId(bpSessionId));
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId);
+        assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId);
+
+        mSessionListener.onSessionEnded(StatusBarManager.SESSION_KEYGUARD,
+                InstanceId.fakeInstanceId(keyguardSessionId));
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId);
+        assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+
+        mSessionListener.onSessionEnded(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+                InstanceId.fakeInstanceId(bpSessionId));
+
+        assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+        assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+    }
+
+    @Test
+    public void testUpdate() throws RemoteException {
+        mListener.onDozeChanged(false);
+        OperationContext context = mProvider.updateContext(mOpContext, false /* crypto */);
+
+        // default state when nothing has been set
+        assertThat(context).isSameInstanceAs(mOpContext);
+        assertThat(mOpContext.id).isEqualTo(0);
+        assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
+        assertThat(mOpContext.isAoD).isEqualTo(false);
+        assertThat(mOpContext.isCrypto).isEqualTo(false);
+
+        for (int type : List.of(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+                StatusBarManager.SESSION_KEYGUARD)) {
+            final int id = 40 + type;
+            final boolean aod = (type & 1) == 0;
+
+            mListener.onDozeChanged(aod);
+            mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id));
+            context = mProvider.updateContext(mOpContext, false /* crypto */);
+            assertThat(context).isSameInstanceAs(mOpContext);
+            assertThat(mOpContext.id).isEqualTo(id);
+            assertThat(mOpContext.reason).isEqualTo(reason(type));
+            assertThat(mOpContext.isAoD).isEqualTo(aod);
+            assertThat(mOpContext.isCrypto).isEqualTo(false);
+
+            mSessionListener.onSessionEnded(type, InstanceId.fakeInstanceId(id));
+        }
+
+        context = mProvider.updateContext(mOpContext, false /* crypto */);
+        assertThat(context).isSameInstanceAs(mOpContext);
+        assertThat(mOpContext.id).isEqualTo(0);
+        assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
+        assertThat(mOpContext.isAoD).isEqualTo(false);
+        assertThat(mOpContext.isCrypto).isEqualTo(false);
+    }
+
+    private static byte reason(int type) {
+        if (type == StatusBarManager.SESSION_BIOMETRIC_PROMPT) {
+            return OperationReason.BIOMETRIC_PROMPT;
+        }
+        if (type == StatusBarManager.SESSION_KEYGUARD) {
+            return OperationReason.KEYGUARD;
+        }
+        return OperationReason.UNKNOWN;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 2b72fab..fe023374 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -31,6 +31,7 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.input.InputSensorInfo;
 import android.platform.test.annotations.Presubmit;
 import android.testing.TestableContext;
@@ -44,7 +45,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 @Presubmit
 @SmallTest
@@ -55,6 +57,9 @@
     private static final int DEFAULT_CLIENT = BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT;
 
     @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Rule
     public TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getContext());
     @Mock
@@ -64,12 +69,12 @@
     @Mock
     private BaseClientMonitor mClient;
 
+    private OperationContext mOpContext;
     private BiometricLogger mLogger;
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
+        mOpContext = new OperationContext();
         mContext.addMockSystemService(SensorManager.class, mSensorManager);
         when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(
                 new Sensor(new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0,
@@ -91,14 +96,13 @@
 
         final int acquiredInfo = 2;
         final int vendorCode = 3;
-        final boolean isCrypto = true;
         final int targetUserId = 9;
 
-        mLogger.logOnAcquired(mContext, acquiredInfo, vendorCode, isCrypto, targetUserId);
+        mLogger.logOnAcquired(mContext, mOpContext, acquiredInfo, vendorCode, targetUserId);
 
-        verify(mSink).acquired(
+        verify(mSink).acquired(eq(mOpContext),
                 eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
-                eq(acquiredInfo), eq(vendorCode), eq(isCrypto), eq(targetUserId));
+                eq(acquiredInfo), eq(vendorCode), eq(targetUserId));
     }
 
     @Test
@@ -107,17 +111,16 @@
 
         final boolean authenticated = true;
         final boolean requireConfirmation = false;
-        final boolean isCrypto = false;
         final int targetUserId = 11;
         final boolean isBiometricPrompt = true;
 
-        mLogger.logOnAuthenticated(mContext,
-                authenticated, requireConfirmation, isCrypto, targetUserId, isBiometricPrompt);
+        mLogger.logOnAuthenticated(mContext, mOpContext,
+                authenticated, requireConfirmation, targetUserId, isBiometricPrompt);
 
-        verify(mSink).authenticate(
+        verify(mSink).authenticate(eq(mOpContext),
                 eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
-                anyLong(), eq(authenticated), anyInt(), eq(requireConfirmation), eq(isCrypto),
-                eq(targetUserId), eq(isBiometricPrompt), anyFloat());
+                anyLong(), anyInt(), eq(requireConfirmation),
+                eq(targetUserId), anyFloat());
     }
 
     @Test
@@ -141,14 +144,13 @@
 
         final int error = 7;
         final int vendorCode = 11;
-        final boolean isCrypto = false;
         final int targetUserId = 9;
 
-        mLogger.logOnError(mContext, error, vendorCode, isCrypto, targetUserId);
+        mLogger.logOnError(mContext, mOpContext, error, vendorCode, targetUserId);
 
-        verify(mSink).error(
+        verify(mSink).error(eq(mOpContext),
                 eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
-                anyLong(), eq(error), eq(vendorCode), eq(isCrypto), eq(targetUserId));
+                anyLong(), eq(error), eq(vendorCode), eq(targetUserId));
     }
 
     @Test
@@ -173,38 +175,34 @@
 
     private void testDisabledMetrics(boolean isBadConfig) {
         mLogger.disableMetrics();
-        mLogger.logOnAcquired(mContext,
+        mLogger.logOnAcquired(mContext, mOpContext,
                 0 /* acquiredInfo */,
                 1 /* vendorCode */,
-                true /* isCrypto */,
                 8 /* targetUserId */);
-        mLogger.logOnAuthenticated(mContext,
+        mLogger.logOnAuthenticated(mContext, mOpContext,
                 true /* authenticated */,
                 true /* requireConfirmation */,
-                false /* isCrypto */,
                 4 /* targetUserId */,
                 true/* isBiometricPrompt */);
         mLogger.logOnEnrolled(2 /* targetUserId */,
                 10 /* latency */,
                 true /* enrollSuccessful */);
-        mLogger.logOnError(mContext,
+        mLogger.logOnError(mContext, mOpContext,
                 4 /* error */,
                 0 /* vendorCode */,
-                false /* isCrypto */,
                 6 /* targetUserId */);
 
-        verify(mSink, never()).acquired(
+        verify(mSink, never()).acquired(eq(mOpContext),
                 anyInt(), anyInt(), anyInt(), anyBoolean(),
-                anyInt(), anyInt(), anyBoolean(), anyInt());
-        verify(mSink, never()).authenticate(
+                anyInt(), anyInt(), anyInt());
+        verify(mSink, never()).authenticate(eq(mOpContext),
                 anyInt(), anyInt(), anyInt(), anyBoolean(),
-                anyLong(), anyBoolean(), anyInt(), anyBoolean(),
-                anyBoolean(), anyInt(), anyBoolean(), anyFloat());
+                anyLong(), anyInt(), anyBoolean(), anyInt(), anyFloat());
         verify(mSink, never()).enroll(
                 anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat());
-        verify(mSink, never()).error(
+        verify(mSink, never()).error(eq(mOpContext),
                 anyInt(), anyInt(), anyInt(), anyBoolean(),
-                anyLong(), anyInt(), anyInt(), anyBoolean(), anyInt());
+                anyLong(), anyInt(), anyInt(), anyInt());
 
         mLogger.logUnknownEnrollmentInFramework();
         mLogger.logUnknownEnrollmentInHal();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index 9bb722f..2d9d868 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -34,6 +35,9 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -92,9 +96,8 @@
                 @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
                 @NonNull ClientMonitorCallbackConverter callback) {
             super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */,
-                    TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */, 0 /* statsModality */,
-                    0 /* statsAction */,
-                    0 /* statsClient */);
+                    TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */,
+                    mock(BiometricLogger.class), mock(BiometricContext.class));
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
index 51d234d..8e6d90c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
@@ -30,6 +30,7 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 
 import org.junit.Before;
@@ -49,6 +50,8 @@
     @Mock
     private BiometricLogger mLogger;
     @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
     private ClientMonitorCallback mCallback;
 
     private TestClientMonitor mClientMonitor;
@@ -109,7 +112,7 @@
 
         TestClientMonitor() {
             super(mContext, mToken, mListener, 9 /* userId */, "foo" /* owner */, 2 /* cookie */,
-                    5 /* sensorId */, mLogger);
+                    5 /* sensorId */, mLogger, mBiometricContext);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 8751cf3..64be569 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -36,6 +36,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,7 +57,8 @@
     public abstract static class InterruptableMonitor<T>
             extends HalClientMonitor<T> implements  Interruptable {
         public InterruptableMonitor() {
-            super(null, null, null, null, 0, null, 0, 0, 0, 0, 0);
+            super(null, null, null, null, 0, null, 0, 0,
+                    mock(BiometricLogger.class), mock(BiometricContext.class));
         }
     }
 
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 ecd9abc..0fa2b41 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
@@ -51,6 +51,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
 import com.android.server.biometrics.nano.BiometricsProto;
 
@@ -526,10 +528,10 @@
                 @NonNull ClientMonitorCallbackConverter listener) {
             super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
                     false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
-                    TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
-                    0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
-                    false /* isKeyguard */, true /* shouldVibrate */,
-                    false /* isKeyguardBypassEnabled */);
+                    TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class),
+                    true /* isStrongBiometric */, null /* taskStackListener */,
+                    mock(LockoutTracker.class), false /* isKeyguard */,
+                    true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
         }
 
         @Override
@@ -573,8 +575,9 @@
                 @NonNull ClientMonitorCallbackConverter listener) {
             super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
                     "test" /* owner */, mock(BiometricUtils.class),
-                    5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
-                    true /* shouldVibrate */);
+                    5 /* timeoutSec */, TEST_SENSOR_ID,
+                    true /* shouldVibrate */,
+                    mock(BiometricLogger.class), mock(BiometricContext.class));
         }
 
         @Override
@@ -613,8 +616,8 @@
         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, 0 /* statsModality */,
-                    0 /* statsAction */, 0 /* statsClient */);
+                    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/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
index 587bb60..092ca19 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -64,7 +64,7 @@
         ClientMonitorCompositeCallback callback = new ClientMonitorCompositeCallback(callbacks);
 
         callback.onClientStarted(mClientMonitor);
-        final InOrder order = inOrder(expected);
+        final InOrder order = inOrder((Object[]) expected);
         for (ClientMonitorCallback cb : expected) {
             order.verify(cb).onClientStarted(eq(mClientMonitor));
         }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 30777cd..52eee9a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -41,6 +41,9 @@
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,6 +69,10 @@
     private Context mContext;
     @Mock
     private IBiometricService mBiometricService;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
     private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
@@ -88,7 +95,8 @@
                     @Override
                     public StopUserClient<?> getStopUserClient(int userId) {
                         return new TestStopUserClient(mContext, Object::new, mToken, userId,
-                                TEST_SENSOR_ID, mUserStoppedCallback);
+                                TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+                                mUserStoppedCallback);
                     }
 
                     @NonNull
@@ -96,7 +104,8 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         mStartUserClientCount++;
                         return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
-                                TEST_SENSOR_ID, mUserStartedCallback, mStartOperationsFinish);
+                                TEST_SENSOR_ID,  mBiometricLogger, mBiometricContext,
+                                mUserStartedCallback, mStartOperationsFinish);
                     }
                 },
                 CoexCoordinator.getInstance());
@@ -226,8 +235,10 @@
     private static class TestStopUserClient extends StopUserClient<Object> {
         public TestStopUserClient(@NonNull Context context,
                 @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
-                int sensorId, @NonNull UserStoppedCallback callback) {
-            super(context, lazyDaemon, token, userId, sensorId, callback);
+                int sensorId, @NonNull BiometricLogger logger,
+                @NonNull BiometricContext biometricContext,
+                @NonNull UserStoppedCallback callback) {
+            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
         }
 
         @Override
@@ -254,8 +265,10 @@
 
         public TestStartUserClient(@NonNull Context context,
                 @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
-                int sensorId, @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
-            super(context, lazyDaemon, token, userId, sensorId, callback);
+                int sensorId, @NonNull BiometricLogger logger,
+                @NonNull BiometricContext biometricContext,
+                @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
+            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
             mShouldFinish = shouldFinish;
         }
 
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
new file mode 100644
index 0000000..aba93b0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+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;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceAuthenticationClientTest {
+
+    private static final int USER_ID = 12;
+    private static final long OP_ID = 32;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private LockoutCache mLockoutCache;
+    @Mock
+    private UsageStats mUsageStats;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Before
+    public void setup() {
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
+    @Test
+    public void authNoContext_v1() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+
+        verify(mHal).authenticate(eq(OP_ID));
+        verify(mHal, never()).authenticateWithContext(anyLong(), any());
+    }
+
+    @Test
+    public void authWithContext_v2() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).authenticateWithContext(
+                eq(OP_ID), same(mOperationContextCaptor.getValue()));
+        verify(mHal, never()).authenticate(anyLong());
+    }
+
+    private FaceAuthenticationClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FaceAuthenticationClient(mContext, () -> aidl, mToken,
+                2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
+                false /* restricted */, "test-owner", 4 /* cookie */,
+                false /* requireConfirmation */, 9 /* sensorId */,
+                mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
+                mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */,
+                false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
new file mode 100644
index 0000000..25135c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceDetectClientTest {
+
+    private static final int USER_ID = 12;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Before
+    public void setup() {
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
+    @Test
+    public void detectNoContext_v1() throws RemoteException {
+        final FaceDetectClient client = createClient(1);
+        client.start(mCallback);
+
+        verify(mHal).detectInteraction();
+        verify(mHal, never()).detectInteractionWithContext(any());
+    }
+
+    @Test
+    public void detectWithContext_v2() throws RemoteException {
+        final FaceDetectClient client = createClient(2);
+        client.start(mCallback);
+
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue()));
+        verify(mHal, never()).detectInteraction();
+    }
+
+    private FaceDetectClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FaceDetectClient(mContext, () -> aidl, mToken,
+                99 /* requestId */, mClientMonitorCallbackConverter, USER_ID,
+                "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+                false /* isStrongBiometric */, null /* sensorPrivacyManager */);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
new file mode 100644
index 0000000..38e048b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyByte;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceEnrollClientTest {
+
+    private static final byte[] HAT = new byte[69];
+    private static final int USER_ID = 12;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Face> mUtils;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Before
+    public void setup() {
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
+    @Test
+    public void enrollNoContext_v1() throws RemoteException {
+        final FaceEnrollClient client = createClient(1);
+        client.start(mCallback);
+
+        verify(mHal).enroll(any(), anyByte(), any(), any());
+        verify(mHal, never()).enrollWithContext(any(), anyByte(), any(), any(), any());
+    }
+
+    @Test
+    public void enrollWithContext_v2() throws RemoteException {
+        final FaceEnrollClient client = createClient(2);
+        client.start(mCallback);
+
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(),
+                same(mOperationContextCaptor.getValue()));
+        verify(mHal, never()).enroll(any(), anyByte(), any(), any());
+    }
+
+    private FaceEnrollClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,
+                USER_ID, HAT, "com.foo.bar", 44 /* requestId */,
+                mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
+                null /* previewSurface */, 8 /* sensorId */,
+                mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */,
+                true /* debugConsent */);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 0ac00aa..12b8264 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -37,6 +37,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -60,6 +61,8 @@
     private UserManager mUserManager;
     @Mock
     private IFace mDaemon;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -89,7 +92,7 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG,
-                mLockoutResetDispatcher);
+                mLockoutResetDispatcher, mBiometricContext);
     }
 
     @SuppressWarnings("rawtypes")
@@ -139,8 +142,9 @@
                 @NonNull Context context,
                 @NonNull SensorProps[] props,
                 @NonNull String halInstanceName,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-            super(context, props, halInstanceName, lockoutResetDispatcher);
+                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+                @NonNull BiometricContext biometricContext) {
+            super(context, props, halInstanceName, lockoutResetDispatcher, biometricContext);
             mDaemon = daemon;
         }
 
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 61e4776..b60324e 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
@@ -32,6 +32,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.CoexCoordinator;
 import com.android.server.biometrics.sensors.LockoutCache;
@@ -66,6 +68,10 @@
     private Sensor.HalSessionCallback.Callback mHalSessionCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -102,7 +108,8 @@
 
         mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext,
                 () -> new AidlSession(1, mSession, USER_ID, mHalCallback),
-                USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher));
+                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext,
+                HAT, mLockoutCache, mLockoutResetDispatcher));
         mLooper.dispatchAll();
 
         verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 21a7a8a..116d2d5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -41,6 +41,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 
@@ -70,6 +71,8 @@
     private UserManager mUserManager;
     @Mock
     private BiometricScheduler mScheduler;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -100,7 +103,8 @@
                 resetLockoutRequiresChallenge);
 
         Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
-        mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
+        mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler,
+                mBiometricContext);
         mBinder = new Binder();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
index 931fad1..ec08329 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
@@ -34,6 +34,8 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 
@@ -62,6 +64,10 @@
     private IFaceServiceReceiver mOtherReceiver;
     @Mock
     private ClientMonitorCallback mMonitorCallback;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private FaceGenerateChallengeClient mClient;
 
@@ -75,7 +81,7 @@
 
         mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(),
                 new ClientMonitorCallbackConverter(mClientReceiver), USER_ID,
-                TAG, SENSOR_ID, START_TIME);
+                TAG, SENSOR_ID, mBiometricLogger, mBiometricContext , START_TIME);
     }
 
     @Test
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
new file mode 100644
index 0000000..de0f038
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
+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;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class FingerprintAuthenticationClientTest {
+
+    private static final int USER_ID = 8;
+    private static final long OP_ID = 7;
+    private static final int POINTER_ID = 0;
+    private static final int TOUCH_X = 8;
+    private static final int TOUCH_Y = 20;
+    private static final float TOUCH_MAJOR = 4.4f;
+    private static final float TOUCH_MINOR = 5.5f;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private LockoutCache mLockoutCache;
+    @Mock
+    private IUdfpsOverlayController mUdfpsOverlayController;
+    @Mock
+    private ISidefpsController mSideFpsController;
+    @Mock
+    private FingerprintSensorPropertiesInternal mSensorProps;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Mock
+    private Probe mLuxProbe;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+    @Captor
+    private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Before
+    public void setup() {
+        when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+                new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
+    @Test
+    public void authNoContext_v1() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+
+        verify(mHal).authenticate(eq(OP_ID));
+        verify(mHal, never()).authenticateWithContext(anyLong(), any());
+    }
+
+    @Test
+    public void authWithContext_v2() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).authenticateWithContext(
+                eq(OP_ID), same(mOperationContextCaptor.getValue()));
+        verify(mHal, never()).authenticate(anyLong());
+    }
+
+    @Test
+    public void pointerUp_v1() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+        client.onPointerUp();
+
+        verify(mHal).onPointerUp(eq(POINTER_ID));
+        verify(mHal, never()).onPointerUpWithContext(any());
+    }
+
+    @Test
+    public void pointerDown_v1() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        verify(mHal).onPointerDown(eq(0),
+                eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+        verify(mHal, never()).onPointerDownWithContext(any());
+    }
+
+    @Test
+    public void pointerUpWithContext_v2() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onPointerUp();
+
+        verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+        verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+
+        final PointerContext pContext = mPointerContextCaptor.getValue();
+        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+    }
+
+    @Test
+    public void pointerDownWithContext_v2() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+        verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
+
+        final PointerContext pContext = mPointerContextCaptor.getValue();
+        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+    }
+
+    @Test
+    public void luxProbeWhenFingerDown() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+        verify(mLuxProbe).enable();
+
+        client.onAcquired(2, 0);
+        verify(mLuxProbe, never()).disable();
+
+        client.onPointerUp();
+        verify(mLuxProbe).disable();
+
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+        verify(mLuxProbe, times(2)).enable();
+    }
+
+    @Test
+    public void notifyHalWhenContextChanges() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+
+        verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+        OperationContext opContext = mOperationContextCaptor.getValue();
+
+        // fake an update to the context
+        verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+        mContextInjector.getValue().accept(opContext);
+        verify(mHal).onContextChanged(eq(opContext));
+
+        client.stopHalOperation();
+        verify(mBiometricContext).unsubscribe(same(opContext));
+    }
+
+    @Test
+    public void showHideOverlay_cancel() throws RemoteException {
+        showHideOverlay(c -> c.cancel());
+    }
+
+    @Test
+    public void showHideOverlay_stop() throws RemoteException {
+        showHideOverlay(c -> c.stopHalOperation());
+    }
+
+    @Test
+    public void showHideOverlay_error() throws RemoteException {
+        showHideOverlay(c -> c.onError(0, 0));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
+    @Test
+    public void showHideOverlay_lockout() throws RemoteException {
+        showHideOverlay(c -> c.onLockoutTimed(5000));
+    }
+
+    @Test
+    public void showHideOverlay_lockoutPerm() throws RemoteException {
+        showHideOverlay(c -> c.onLockoutPermanent());
+    }
+
+    private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block)
+            throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+
+        client.start(mCallback);
+
+        verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any());
+        verify(mSideFpsController).show(anyInt(), anyInt());
+
+        block.accept(client);
+
+        verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+        verify(mSideFpsController).hide(anyInt());
+    }
+
+    private FingerprintAuthenticationClient createClient() throws RemoteException {
+        return createClient(100);
+    }
+
+    private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
+                2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
+        false /* restricted */, "test-owner", 4 /* cookie */, false /* requireConfirmation */,
+        9 /* sensorId */, mBiometricLogger, mBiometricContext,
+        true /* isStrongBiometric */,
+        null /* taskStackListener */, mLockoutCache,
+        mUdfpsOverlayController, mSideFpsController,
+        false /* allowBackgroundAuthentication */, mSensorProps);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
new file mode 100644
index 0000000..93cbef1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintDetectClientTest {
+
+    private static final int USER_ID = 8;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private IUdfpsOverlayController mUdfpsOverlayController;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Before
+    public void setup() {
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
+    @Test
+    public void detectNoContext_v1() throws RemoteException {
+        final FingerprintDetectClient client = createClient(1);
+
+        client.start(mCallback);
+
+        verify(mHal).detectInteraction();
+        verify(mHal, never()).detectInteractionWithContext(any());
+    }
+
+    @Test
+    public void detectNoContext_v2() throws RemoteException {
+        final FingerprintDetectClient client = createClient(2);
+
+        client.start(mCallback);
+
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue()));
+        verify(mHal, never()).detectInteraction();
+    }
+
+    private FingerprintDetectClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FingerprintDetectClient(mContext, () -> aidl, mToken,
+                6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
+                "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
+                mUdfpsOverlayController, true /* isStrongBiometric */);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
new file mode 100644
index 0000000..5a96f5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class FingerprintEnrollClientTest {
+
+    private static final byte[] HAT = new byte[69];
+    private static final int USER_ID = 8;
+    private static final int POINTER_ID = 0;
+    private static final int TOUCH_X = 8;
+    private static final int TOUCH_Y = 20;
+    private static final float TOUCH_MAJOR = 4.4f;
+    private static final float TOUCH_MINOR = 5.5f;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private ISession mHal;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Fingerprint> mBiometricUtils;
+    @Mock
+    private IUdfpsOverlayController mUdfpsOverlayController;
+    @Mock
+    private ISidefpsController mSideFpsController;
+    @Mock
+    private FingerprintSensorPropertiesInternal mSensorProps;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Sensor.HalSessionCallback mHalSessionCallback;
+    @Mock
+    private Probe mLuxProbe;
+    @Captor
+    private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+    @Captor
+    private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Before
+    public void setup() {
+        when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+                new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
+        when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+                i -> i.getArgument(0));
+    }
+
+    @Test
+    public void enrollNoContext_v1() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(1);
+
+        client.start(mCallback);
+
+        verify(mHal).enroll(any());
+        verify(mHal, never()).enrollWithContext(any(), any());
+    }
+
+    @Test
+    public void enrollWithContext_v2() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(2);
+
+        client.start(mCallback);
+
+        InOrder order = inOrder(mHal, mBiometricContext);
+        order.verify(mBiometricContext).updateContext(
+                mOperationContextCaptor.capture(), anyBoolean());
+        order.verify(mHal).enrollWithContext(any(), same(mOperationContextCaptor.getValue()));
+        verify(mHal, never()).enroll(any());
+    }
+
+    @Test
+    public void pointerUp_v1() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(1);
+        client.start(mCallback);
+        client.onPointerUp();
+
+        verify(mHal).onPointerUp(eq(POINTER_ID));
+        verify(mHal, never()).onPointerUpWithContext(any());
+    }
+
+    @Test
+    public void pointerDown_v1() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(1);
+        client.start(mCallback);
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        verify(mHal).onPointerDown(eq(0),
+                eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+        verify(mHal, never()).onPointerDownWithContext(any());
+    }
+
+    @Test
+    public void pointerUpWithContext_v2() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(2);
+        client.start(mCallback);
+        client.onPointerUp();
+
+        verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+        verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+
+        final PointerContext pContext = mPointerContextCaptor.getValue();
+        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+    }
+
+    @Test
+    public void pointerDownWithContext_v2() throws RemoteException {
+        final FingerprintEnrollClient client = createClient(2);
+        client.start(mCallback);
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+        verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
+
+        final PointerContext pContext = mPointerContextCaptor.getValue();
+        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+    }
+
+    @Test
+    public void luxProbeWhenFingerDown() throws RemoteException {
+        final FingerprintEnrollClient client = createClient();
+        client.start(mCallback);
+
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+        verify(mLuxProbe).enable();
+
+        client.onAcquired(2, 0);
+        verify(mLuxProbe, never()).disable();
+
+        client.onPointerUp();
+        verify(mLuxProbe).disable();
+
+        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+        verify(mLuxProbe, times(2)).enable();
+    }
+
+    @Test
+    public void notifyHalWhenContextChanges() throws RemoteException {
+        final FingerprintEnrollClient client = createClient();
+        client.start(mCallback);
+
+        verify(mHal).enrollWithContext(any(), mOperationContextCaptor.capture());
+        OperationContext opContext = mOperationContextCaptor.getValue();
+
+        // fake an update to the context
+        verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+        mContextInjector.getValue().accept(opContext);
+        verify(mHal).onContextChanged(eq(opContext));
+
+        client.stopHalOperation();
+        verify(mBiometricContext).unsubscribe(same(opContext));
+    }
+
+    @Test
+    public void showHideOverlay_cancel() throws RemoteException {
+        showHideOverlay(c -> c.cancel());
+    }
+
+    @Test
+    public void showHideOverlay_stop() throws RemoteException {
+        showHideOverlay(c -> c.stopHalOperation());
+    }
+
+    @Test
+    public void showHideOverlay_error() throws RemoteException {
+        showHideOverlay(c -> c.onError(0, 0));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
+    @Test
+    public void showHideOverlay_result() throws RemoteException {
+        showHideOverlay(c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0));
+    }
+
+    private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
+            throws RemoteException {
+        final FingerprintEnrollClient client = createClient();
+
+        client.start(mCallback);
+
+        verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any());
+        verify(mSideFpsController).show(anyInt(), anyInt());
+
+        block.accept(client);
+
+        verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+        verify(mSideFpsController).hide(anyInt());
+    }
+
+    private FingerprintEnrollClient createClient() throws RemoteException {
+        return createClient(500);
+    }
+
+    private FingerprintEnrollClient createClient(int version) throws RemoteException {
+        when(mHal.getInterfaceVersion()).thenReturn(version);
+
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        return new FingerprintEnrollClient(mContext, () -> aidl, mToken, 6 /* requestId */,
+        mClientMonitorCallbackConverter, 0 /* userId */,
+        HAT, "owner", mBiometricUtils, 8 /* sensorId */,
+        mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
+        mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 73f1516..5a1a02e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -40,6 +40,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -71,6 +72,8 @@
     private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @Mock
     private FingerprintStateCallback mFingerprintStateCallback;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -105,7 +108,7 @@
 
         mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
                 mFingerprintStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher);
+                mGestureAvailabilityDispatcher, mBiometricContext);
     }
 
     @SuppressWarnings("rawtypes")
@@ -157,9 +160,10 @@
                 @NonNull SensorProps[] props,
                 @NonNull String halInstanceName,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+                @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+                @NonNull BiometricContext biometricContext) {
             super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                    gestureAvailabilityDispatcher);
+                    gestureAvailabilityDispatcher, biometricContext);
             mDaemon = daemon;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 8b7b484..e1a4a2d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -32,6 +32,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.CoexCoordinator;
 import com.android.server.biometrics.sensors.LockoutCache;
@@ -66,6 +68,10 @@
     private Sensor.HalSessionCallback.Callback mHalSessionCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
+    @Mock
+    private BiometricLogger mLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -102,7 +108,8 @@
 
         mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext,
                 () -> new AidlSession(1, mSession, USER_ID, mHalCallback),
-                USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher));
+                USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT, mLockoutCache,
+                mLockoutResetDispatcher));
         mLooper.dispatchAll();
 
         verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index f6b9209..529f994 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
@@ -70,6 +71,8 @@
     private BiometricScheduler mScheduler;
     @Mock
     private FingerprintStateCallback mFingerprintStateCallback;
+    @Mock
+    private BiometricContext mBiometricContext;
 
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private Fingerprint21 mFingerprint21;
@@ -101,7 +104,7 @@
 
         mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
                 mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
-                mHalResultController);
+                mHalResultController, mBiometricContext);
     }
 
     @Test
@@ -126,9 +129,10 @@
                 @NonNull FingerprintSensorPropertiesInternal sensorProps,
                 @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull HalResultController controller) {
+                @NonNull HalResultController controller,
+                @NonNull BiometricContext biometricContext) {
             super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
-                    lockoutResetDispatcher, controller);
+                    lockoutResetDispatcher, controller, biometricContext);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ff1b6f6..83fa7ac 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -17,17 +17,23 @@
 package com.android.server.companion.virtual;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IInputConstants;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
+import android.view.DisplayInfo;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -46,18 +52,31 @@
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
+    private DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock
     private InputController.NativeWrapper mNativeWrapperMock;
+    @Mock
+    private IInputManager mIInputManagerMock;
 
     private InputController mInputController;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.uniqueId = "uniqueId";
+        doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+        InputManager.resetInstance(mIInputManagerMock);
+        doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+        doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
         mInputController = new InputController(new Object(), mNativeWrapperMock);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e36263e..33540c8 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -57,6 +58,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.DisplayInfo;
 import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
@@ -80,6 +82,8 @@
     private static final int DISPLAY_ID = 2;
     private static final int PRODUCT_ID = 10;
     private static final int VENDOR_ID = 5;
+    private static final String UNIQUE_ID = "uniqueid";
+    private static final String PHYS = "phys";
     private static final int HEIGHT = 1800;
     private static final int WIDTH = 900;
     private static final Binder BINDER = new Binder("binder");
@@ -116,6 +120,12 @@
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.uniqueId = UNIQUE_ID;
+        doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
         mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         doNothing().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
@@ -150,11 +160,11 @@
         mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
         verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), anyInt());
+                nullable(String.class), anyInt(), eq(null));
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(displayId));
+                nullable(String.class), eq(displayId), eq(null));
     }
 
     @Test
@@ -167,7 +177,7 @@
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(displayId));
+                nullable(String.class), eq(displayId), eq(null));
     }
 
     @Test
@@ -186,7 +196,7 @@
         verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
                 anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(displayId));
+                nullable(String.class), eq(displayId), eq(null));
 
         IBinder wakeLock = wakeLockCaptor.getValue();
         mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
@@ -202,7 +212,7 @@
         verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
                 anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(displayId));
+                nullable(String.class), eq(displayId), eq(null));
         IBinder wakeLock = wakeLockCaptor.getValue();
 
         // Close the VirtualDevice without first notifying it of the VirtualDisplay removal.
@@ -274,7 +284,8 @@
                 BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
-        verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+        verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
+                eq(PRODUCT_ID), anyString());
     }
 
     @Test
@@ -284,7 +295,8 @@
                 BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
-        verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+        verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
+                anyString());
     }
 
     @Test
@@ -294,8 +306,8 @@
                 BINDER, new Point(WIDTH, HEIGHT));
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
-        verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT,
-                WIDTH);
+        verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
+                eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
     }
 
     @Test
@@ -315,7 +327,7 @@
         final int action = VirtualKeyEvent.ACTION_UP;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
                 .setAction(action).build());
         verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
@@ -340,7 +352,7 @@
         final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
                 .setButtonCode(buttonCode)
@@ -355,7 +367,7 @@
         final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -381,7 +393,7 @@
         final float y = 0.7f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
                 .setRelativeX(x).setRelativeY(y).build());
@@ -395,7 +407,7 @@
         final float y = 0.7f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -422,7 +434,7 @@
         final float y = 1f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
                 .setXAxisMovement(x)
@@ -437,7 +449,7 @@
         final float y = 1f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -470,7 +482,7 @@
         final int action = VirtualTouchEvent.ACTION_UP;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
                 .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
         verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
@@ -489,7 +501,7 @@
         final float majorAxisSize = 10.0f;
         mInputController.mInputDeviceDescriptors.put(BINDER,
                 new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
-                        /* displayId= */ 1));
+                        /* displayId= */ 1, PHYS));
         mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
                 .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
                 .setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 564c4e4..c877bd1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -28,6 +28,14 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
@@ -101,6 +109,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -138,6 +147,9 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
+import com.android.server.pm.RestrictionsSet;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserRestrictionsUtils;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
@@ -7725,30 +7737,20 @@
 
         dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
 
-        int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
-        assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
-        assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
-
+        assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
         initializeDpms();
-
-        returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
-        assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
-        assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
+        assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
     }
 
     @Test
-    public void testSetDeviceOwnerType_asDeviceOwner_throwsExceptionWhenSetDeviceOwnerTypeAgain()
+    public void testSetDeviceOwnerType_asDeviceOwner_setDeviceOwnerTypeTwice_success()
             throws Exception {
         setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT);
 
         dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
 
-        int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
-        assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
-        assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
-
-        assertThrows(IllegalStateException.class,
-                () -> dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT));
+        assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
     }
 
     @Test
@@ -7764,6 +7766,296 @@
     }
 
     @Test
+    public void testSetUserRestriction_financeDo_invalidRestrictions_restrictionNotSet()
+            throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+            if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+                assertNoDeviceOwnerRestrictions();
+                assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                        () -> dpm.addUserRestriction(admin1, restriction));
+
+                verify(getServices().userManagerInternal, never())
+                        .setDevicePolicyUserRestrictions(anyInt(), any(), any(), anyBoolean());
+            }
+        }
+    }
+
+    @Test
+    public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction()
+            throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+            if (UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+                assertNoDeviceOwnerRestrictions();
+                dpm.addUserRestriction(admin1, restriction);
+
+                Bundle globalRestrictions =
+                        dpms.getDeviceOwnerAdminLocked().getGlobalUserRestrictions(
+                                UserManagerInternal.OWNER_TYPE_DEVICE_OWNER);
+                RestrictionsSet localRestrictions = new RestrictionsSet();
+                localRestrictions.updateRestrictions(
+                        UserHandle.USER_SYSTEM,
+                        dpms.getDeviceOwnerAdminLocked().getLocalUserRestrictions(
+                                UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
+                verify(getServices().userManagerInternal)
+                        .setDevicePolicyUserRestrictions(eq(UserHandle.USER_SYSTEM),
+                                MockUtils.checkUserRestrictions(globalRestrictions),
+                                MockUtils.checkUserRestrictions(
+                                        UserHandle.USER_SYSTEM, localRestrictions),
+                                eq(true));
+                reset(getServices().userManagerInternal);
+
+                dpm.clearUserRestriction(admin1, restriction);
+                reset(getServices().userManagerInternal);
+            }
+        }
+    }
+
+    @Test
+    public void testSetLockTaskFeatures_financeDo_validLockTaskFeatures_lockTaskFeaturesSet()
+            throws Exception {
+        int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+                | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+                | LOCK_TASK_FEATURE_NOTIFICATIONS;
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setLockTaskFeatures(admin1, validLockTaskFeatures);
+
+        verify(getServices().iactivityTaskManager)
+                .updateLockTaskFeatures(eq(UserHandle.USER_SYSTEM), eq(validLockTaskFeatures));
+    }
+
+    @Test
+    public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException()
+            throws Exception {
+        int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW
+                | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+        // Called during setup.
+        verify(getServices().iactivityTaskManager).updateLockTaskFeatures(anyInt(), anyInt());
+
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setLockTaskFeatures(admin1, invalidLockTaskFeatures));
+
+        verifyNoMoreInteractions(getServices().iactivityTaskManager);
+    }
+
+    @Test
+    public void testIsUninstallBlocked_financeDo_success() throws Exception {
+        String packageName = "com.android.foo.package";
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+        when(getServices().ipackageManager.getBlockUninstallForUser(
+                eq(packageName), eq(UserHandle.USER_SYSTEM)))
+                .thenReturn(true);
+
+        assertThat(dpm.isUninstallBlocked(admin1, packageName)).isTrue();
+    }
+
+    @Test
+    public void testSetUninstallBlocked_financeDo_success() throws Exception {
+        String packageName = "com.android.foo.package";
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setUninstallBlocked(admin1, packageName, false);
+
+        verify(getServices().ipackageManager)
+                .setBlockUninstallForUser(eq(packageName), eq(false),
+                        eq(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testSetUserControlDisabledPackages_financeDo_success() throws Exception {
+        List<String> packages = new ArrayList<>();
+        packages.add("com.android.foo.package");
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setUserControlDisabledPackages(admin1, packages);
+
+        verify(getServices().packageManagerInternal)
+                .setDeviceOwnerProtectedPackages(eq(admin1.getPackageName()), eq(packages));
+    }
+
+    @Test
+    public void testGetUserControlDisabledPackages_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertThat(dpm.getUserControlDisabledPackages(admin1)).isEmpty();
+    }
+
+    @Test
+    public void testSetOrganizationName_financeDo_success() throws Exception {
+        String organizationName = "Test Organization";
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setOrganizationName(admin1, organizationName);
+
+        assertThat(dpm.getDeviceOwnerOrganizationName()).isEqualTo(organizationName);
+    }
+
+    @Test
+    public void testSetShortSupportMessage_financeDo_success() throws Exception {
+        String supportMessage = "Test short support message";
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setShortSupportMessage(admin1, supportMessage);
+
+        assertThat(dpm.getShortSupportMessage(admin1)).isEqualTo(supportMessage);
+    }
+
+    @Test
+    public void testIsBackupServiceEnabled_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+        when(getServices().ibackupManager.isBackupServiceActive(eq(UserHandle.USER_SYSTEM)))
+                .thenReturn(true);
+
+        assertThat(dpm.isBackupServiceEnabled(admin1)).isTrue();
+    }
+
+    @Test
+    public void testSetBackupServiceEnabled_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setBackupServiceEnabled(admin1, true);
+
+        verify(getServices().ibackupManager)
+                .setBackupServiceActive(eq(UserHandle.USER_SYSTEM), eq(true));
+    }
+
+    @Test
+    public void testIsLockTaskPermitted_financeDo_success() throws Exception {
+        String packageName = "com.android.foo.package";
+        mockPolicyExemptApps(packageName);
+        mockVendorPolicyExemptApps();
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertThat(dpm.isLockTaskPermitted(packageName)).isTrue();
+    }
+
+    @Test
+    public void testSetLockTaskPackages_financeDo_success() throws Exception {
+        String[] packages = {"com.android.foo.package"};
+        mockEmptyPolicyExemptApps();
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.setLockTaskPackages(admin1, packages);
+
+        verify(getServices().iactivityManager)
+                .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(packages));
+    }
+
+    @Test
+    public void testAddPersistentPreferredActivity_financeDo_success() throws Exception {
+        IntentFilter filter = new IntentFilter();
+        ComponentName target = new ComponentName(admin2.getPackageName(), "test.class");
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.addPersistentPreferredActivity(admin1, filter, target);
+
+        verify(getServices().ipackageManager)
+                .addPersistentPreferredActivity(eq(filter), eq(target), eq(UserHandle.USER_SYSTEM));
+        verify(getServices().ipackageManager)
+                .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception {
+        String packageName = admin2.getPackageName();
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.clearPackagePersistentPreferredActivities(admin1, packageName);
+
+        verify(getServices().ipackageManager)
+                .clearPackagePersistentPreferredActivities(
+                        eq(packageName), eq(UserHandle.USER_SYSTEM));
+        verify(getServices().ipackageManager)
+                .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testWipeData_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+        when(getServices().userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_FACTORY_RESET,
+                UserHandle.SYSTEM))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+        when(mMockContext.getResources()
+                .getString(R.string.work_profile_deleted_description_dpm_wipe))
+                .thenReturn("Test string");
+
+        dpm.wipeData(0);
+
+        verifyRebootWipeUserData(/* wipeEuicc= */ false);
+    }
+
+    @Test
+    public void testIsDeviceOwnerApp_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertThat(dpm.isDeviceOwnerApp(admin1.getPackageName())).isTrue();
+    }
+
+    @Test
+    public void testClearDeviceOwnerApp_financeDo_success() throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        dpm.clearDeviceOwnerApp(admin1.getPackageName());
+
+        assertThat(dpm.getDeviceOwnerComponentOnAnyUser()).isNull();
+        assertThat(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM)).isFalse();
+        verify(mMockContext.spiedContext, times(2))
+                .sendBroadcastAsUser(
+                        MockUtils.checkIntentAction(
+                                DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
+                        eq(UserHandle.SYSTEM));
+    }
+
+    @Test
+    public void testSetPermissionGrantState_financeDo_notReadPhoneStatePermission_throwsException()
+            throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setPermissionGrantState(admin1, admin1.getPackageName(),
+                        permission.READ_CALENDAR,
+                        DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+    }
+
+    @Test
+    public void testSetPermissionGrantState_financeDo_grantPermissionToNonDeviceOwnerPackage_throwsException()
+            throws Exception {
+        setDeviceOwner();
+        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setPermissionGrantState(admin1, "com.android.foo.package",
+                        permission.READ_PHONE_STATE,
+                        DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+    }
+
+    @Test
     public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() {
         assertThrows(SecurityException.class,
                 () -> dpm.setUsbDataSignalingEnabled(true));
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 15f3ed1..4cb46b4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -109,6 +109,14 @@
     public static Bundle checkUserRestrictions(String... keys) {
         final Bundle expected = DpmTestUtils.newRestrictions(
                 java.util.Objects.requireNonNull(keys));
+        return checkUserRestrictions(expected);
+    }
+
+    public static Bundle checkUserRestrictions(Bundle expected) {
+        return createUserRestrictionsBundleMatcher(expected);
+    }
+
+    private static Bundle createUserRestrictionsBundleMatcher(Bundle expected) {
         final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
             @Override
             public boolean matches(Object item) {
@@ -129,6 +137,15 @@
     public static RestrictionsSet checkUserRestrictions(int userId, String... keys) {
         final RestrictionsSet expected = DpmTestUtils.newRestrictions(userId,
                 java.util.Objects.requireNonNull(keys));
+        return checkUserRestrictions(userId, expected);
+    }
+
+    public static RestrictionsSet checkUserRestrictions(int userId, RestrictionsSet expected) {
+        return createUserRestrictionsSetMatcher(userId, expected);
+    }
+
+    private static RestrictionsSet createUserRestrictionsSetMatcher(
+            int userId, RestrictionsSet expected) {
         final Matcher<RestrictionsSet> m = new BaseMatcher<RestrictionsSet>() {
             @Override
             public boolean matches(Object item) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index a8f24ce..533fb2d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -26,6 +26,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
 
@@ -76,6 +77,7 @@
     private static final ComponentName TEST_MDM_COMPONENT_NAME = new ComponentName(
             TEST_DPC_PACKAGE_NAME, "pc.package.name.DeviceAdmin");
     private static final int TEST_USER_ID = 123;
+    private static final String ROLE_HOLDER_PACKAGE_NAME = "test.role.holder.package.name";
 
     private @Mock Resources mResources;
 
@@ -305,6 +307,26 @@
                 ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2", "package3");
     }
 
+    @Test
+    public void testGetNonRequiredApps_managedProfile_roleHolder_works() {
+        when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+                .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
+        setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
+
+        verifyAppsAreNonRequired(
+                ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2");
+    }
+
+    @Test
+    public void testGetNonRequiredApps_managedDevice_roleHolder_works() {
+        when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+                .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
+        setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
+
+        verifyAppsAreNonRequired(
+                ACTION_PROVISION_MANAGED_DEVICE, "package1", "package2");
+    }
+
     private void setupRegularModulesWithManagedUser(String... regularModules) {
         setupRegularModulesWithMetadata(regularModules, REQUIRED_APP_MANAGED_USER);
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 02a8ae8..9a5254d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -93,7 +93,7 @@
             assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)).isFalse();
 
             owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
-                    DEVICE_OWNER_TYPE_FINANCED);
+                    DEVICE_OWNER_TYPE_FINANCED, /* isAdminTestOnly= */ false);
             // There is no device owner, so the default owner type should be returned.
             assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
                     DEVICE_OWNER_TYPE_DEFAULT);
@@ -367,7 +367,7 @@
             owners.setDeviceOwnerUserRestrictionsMigrated();
 
             owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
-                    DEVICE_OWNER_TYPE_FINANCED);
+                    DEVICE_OWNER_TYPE_FINANCED, /* isAdminTestOnly= */ false);
             assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
                     DEVICE_OWNER_TYPE_FINANCED);
 
@@ -399,7 +399,7 @@
             owners.setProfileOwnerUserRestrictionsMigrated(11);
 
             owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
-                    DEVICE_OWNER_TYPE_DEFAULT);
+                    DEVICE_OWNER_TYPE_DEFAULT, /* isAdminTestOnly= */ false);
             // The previous device owner type should persist.
             assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
                     DEVICE_OWNER_TYPE_FINANCED);
@@ -585,7 +585,8 @@
         assertThat(owners.getProfileOwnerFile(11).exists()).isTrue();
 
         String previousDeviceOwnerPackageName = owners.getDeviceOwnerPackageName();
-        owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED);
+        owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED,
+                /* isAdminTestOnly= */ false);
         assertThat(owners.getDeviceOwnerType(previousDeviceOwnerPackageName)).isEqualTo(
                 DEVICE_OWNER_TYPE_FINANCED);
         owners.setDeviceOwnerProtectedPackages(
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index d2cff0e..fe3034d 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -326,7 +326,7 @@
         assertEquals(callback.getLastNotifiedInfo().currentState,
                 OTHER_DEVICE_STATE.getIdentifier());
 
-        mService.getBinderService().cancelRequest(token);
+        mService.getBinderService().cancelStateRequest();
         flushHandler();
 
         assertEquals(callback.getLastNotifiedStatus(token),
@@ -378,9 +378,9 @@
         mPolicy.resumeConfigureOnce();
         flushHandler();
 
-        // First request status is now suspended as there is another pending request.
+        // First request status is now canceled as there is another pending request.
         assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
-                TestDeviceStateManagerCallback.STATUS_SUSPENDED);
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
         // Second request status still unknown because the service is still awaiting policy
         // callback.
         assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
@@ -402,19 +402,19 @@
                 DEFAULT_DEVICE_STATE.getIdentifier());
 
         // Now cancel the second request to make the first request active.
-        mService.getBinderService().cancelRequest(secondRequestToken);
+        mService.getBinderService().cancelStateRequest();
         flushHandler();
 
         assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
-                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
         assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
                 TestDeviceStateManagerCallback.STATUS_CANCELED);
 
-        assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
         assertEquals(mService.getPendingState(), Optional.empty());
         assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
         assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
-                OTHER_DEVICE_STATE.getIdentifier());
+                DEFAULT_DEVICE_STATE.getIdentifier());
     }
 
     @Test
@@ -656,11 +656,6 @@
         }
 
         @Override
-        public void onRequestSuspended(IBinder token) {
-            mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
-        }
-
-        @Override
         public void onRequestCanceled(IBinder token) {
             mLastNotifiedStatus.put(token, STATUS_CANCELED);
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index b94fc43..2297c91 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
-import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
@@ -66,7 +65,7 @@
     }
 
     @Test
-    public void addRequest_suspendExistingRequest() {
+    public void addRequest_cancelExistingRequestThroughNewRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* requestedState */, 0 /* flags */);
         assertNull(mStatusListener.getLastStatus(firstRequest));
@@ -75,92 +74,52 @@
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                1 /* requestedState */, 0 /* flags */);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
         mController.addRequest(secondRequest);
         assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
     public void addRequest_cancelActiveRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* requestedState */, 0 /* flags */);
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
 
         mController.addRequest(firstRequest);
-        mController.addRequest(secondRequest);
 
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
-        mController.cancelRequest(secondRequest.getToken());
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
-    }
 
-    @Test
-    public void addRequest_cancelSuspendedRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+        mController.cancelOverrideRequest();
 
-        mController.addRequest(firstRequest);
-        mController.addRequest(secondRequest);
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
-        mController.cancelRequest(firstRequest.getToken());
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
     public void handleBaseStateChanged() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* requestedState */,
                 DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */);
 
         mController.addRequest(firstRequest);
-        mController.addRequest(secondRequest);
 
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         mController.handleBaseStateChanged();
 
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
     public void handleProcessDied() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* requestedState */, 0 /* flags */);
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
 
         mController.addRequest(firstRequest);
-        mController.addRequest(secondRequest);
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
-        mController.handleProcessDied(1);
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         mController.handleProcessDied(0);
-
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
@@ -170,46 +129,29 @@
 
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* requestedState */, 0 /* flags */);
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
 
         mController.addRequest(firstRequest);
-        mController.addRequest(secondRequest);
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
-        mController.handleProcessDied(1);
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
-        mController.cancelStickyRequests();
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+        mController.handleProcessDied(0);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+        mController.cancelStickyRequest();
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
     public void handleNewSupportedStates() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 1 /* requestedState */, 0 /* flags */);
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                2 /* requestedState */, 0 /* flags */);
 
         mController.addRequest(firstRequest);
-        mController.addRequest(secondRequest);
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         mController.handleNewSupportedStates(new int[]{ 0, 1 });
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         mController.handleNewSupportedStates(new int[]{ 0 });
-
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
@@ -217,18 +159,11 @@
     public void cancelOverrideRequestsTest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 1 /* requestedState */, 0 /* flags */);
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                2 /* requestedState */, 0 /* flags */);
 
         mController.addRequest(firstRequest);
-        mController.addRequest(secondRequest);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
-        mController.cancelOverrideRequests();
-
-        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+        mController.cancelOverrideRequest();
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 4caa85c..f5a5689 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -81,6 +81,7 @@
     @Mock HysteresisLevels mScreenBrightnessThresholds;
     @Mock Handler mNoOpHandler;
     @Mock HighBrightnessModeController mHbmController;
+    @Mock BrightnessThrottler mBrightnessThrottler;
 
     @Before
     public void setUp() {
@@ -128,12 +129,15 @@
                 INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
                 DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
                 mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
-                mContext, mHbmController, mIdleBrightnessMappingStrategy,
+                mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
                 AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG
         );
 
         when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
         when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT);
+        // Disable brightness throttling by default. Individual tests can enable it as needed.
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+        when(mBrightnessThrottler.isThrottled()).thenReturn(false);
 
         // Configure the brightness controller and grab an instance of the sensor listener,
         // through which we can deliver fake (for test) sensor values.
@@ -420,4 +424,47 @@
         assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
         assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
     }
+
+    @Test
+    public void testBrightnessGetsThrottled() throws Exception {
+        Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+        mController = setupController(lightSensor);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return max brightness at 100 lux
+        final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT;
+        final float lux = 100.0f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux))
+                .thenReturn(lux);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux))
+                .thenReturn(lux);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
+                .thenReturn(normalizedBrightness);
+
+        // Sensor reads 100 lux. We should get max brightness.
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux));
+        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
+
+        // Apply throttling and notify ABC (simulates DisplayPowerController#updatePowerState())
+        final float throttledBrightness = 0.123f;
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness);
+        when(mBrightnessThrottler.isThrottled()).thenReturn(true);
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
+                BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
+                0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+        assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
+
+        // Remove throttling and notify ABC again
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+        when(mBrightnessThrottler.isThrottled()).thenReturn(false);
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
+                BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
+                0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index b7af010..6203c2f 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -663,6 +663,30 @@
             eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
     }
 
+    @Test
+    public void tetHbmStats_LowRequestedBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        // verify in HBM sunlight mode
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        // verify HBM_ON_SUNLIGHT
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+        // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+            eq(FrameworkStatsLog
+                      .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
+    }
+
     private void assertState(HighBrightnessModeController hbmc,
             float brightnessMin, float brightnessMax, int hbmMode) {
         assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index 0f3742f..ac97911 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -59,6 +59,8 @@
 
 @RunWith(JUnit4.class)
 public final class AmbientLuxTest {
+
+    private static final float ALLOWED_ERROR_DELTA = 0.001f;
     private static final int AMBIENT_COLOR_TYPE = 20705;
     private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
     private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
@@ -78,6 +80,8 @@
     @Mock private TypedArray mHighLightBiases;
     @Mock private TypedArray mAmbientColorTemperatures;
     @Mock private TypedArray mDisplayColorTemperatures;
+    @Mock private TypedArray mStrongAmbientColorTemperatures;
+    @Mock private TypedArray mStrongDisplayColorTemperatures;
     @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
 
     @Before
@@ -110,6 +114,12 @@
         when(mResourcesSpy.obtainTypedArray(
                 R.array.config_displayWhiteBalanceDisplayColorTemperatures))
                 .thenReturn(mDisplayColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures))
+                .thenReturn(mStrongAmbientColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures))
+                .thenReturn(mStrongDisplayColorTemperatures);
 
         when(mResourcesSpy.obtainTypedArray(
                 R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
@@ -375,6 +385,43 @@
     }
 
     @Test
+    public void testStrongMode() {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+        final int ambientColorTempLow = 6000;
+        final int ambientColorTempHigh = 8000;
+        final int displayColorTempLow = 6400;
+        final int displayColorTempHigh = 7400;
+        setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh);
+        setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        controller.setStrongModeEnabled(true);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f;
+                ambientTempFraction += 0.1f) {
+            final float ambientTemp =
+                    (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction
+                            + ambientColorTempLow;
+            setEstimatedColorTemperature(controller, ambientTemp);
+            for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f;
+                    brightnessFraction += 0.1f) {
+                setEstimatedBrightnessAndUpdate(controller,
+                        mix(lowerBrightness, upperBrightness, brightnessFraction));
+                assertEquals(controller.mPendingAmbientColorTemperature,
+                        mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
+                                mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
+                                brightnessFraction),
+                        ALLOWED_ERROR_DELTA);
+            }
+        }
+    }
+
+    @Test
     public void testLowLight_DefaultAmbient() throws Exception {
         final float lowerBrightness = 10.0f;
         final float upperBrightness = 50.0f;
@@ -486,6 +533,14 @@
         setFloatArrayResource(mDisplayColorTemperatures, vals);
     }
 
+    private void setStrongAmbientColorTemperatures(float... vals) {
+        setFloatArrayResource(mStrongAmbientColorTemperatures, vals);
+    }
+
+    private void setStrongDisplayColorTemperatures(float... vals) {
+        setFloatArrayResource(mStrongDisplayColorTemperatures, vals);
+    }
+
     private void setFloatArrayResource(TypedArray array, float[] vals) {
         when(array.length()).thenReturn(vals.length);
         for (int i = 0; i < vals.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index d441143..0028969 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -165,6 +165,17 @@
                 R.bool.config_cecTvSendStandbyOnSleepDisabled_default);
 
         doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSetMenuLanguage_userConfigurable);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSetMenuLanguageEnabled_allowed);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSetMenuLanguageEnabled_default);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSetMenuLanguageDisabled_allowed);
+        doReturn(false).when(resources).getBoolean(
+                R.bool.config_cecSetMenuLanguageDisabled_default);
+
+        doReturn(true).when(resources).getBoolean(
                 R.bool.config_cecRcProfileTv_userConfigurable);
         doReturn(true).when(resources).getBoolean(
                 R.bool.config_cecRcProfileTvNone_allowed);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index 85d30a6..8e756ae 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -83,6 +83,7 @@
                     HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
                     HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                    HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -121,6 +122,7 @@
                     HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
                     HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                    HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -159,6 +161,7 @@
                     HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
                     HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                    HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
                     HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -317,8 +320,10 @@
     @Test
     public void getDefaultStringValue_MultipleDefaults() {
         setBooleanResource(R.bool.config_cecPowerControlModeBroadcast_default, true);
-        assertThrows(RuntimeException.class,
-                () -> new HdmiCecConfig(mContext, mStorageAdapter));
+        HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter);
+        assertThat(hdmiCecConfig.getDefaultStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE))
+                .isEqualTo(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 7751ef5..c48a974 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -42,6 +42,7 @@
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
 import android.hardware.hdmi.IHdmiControlStatusChangeListener;
+import android.hardware.hdmi.IHdmiVendorCommandListener;
 import android.os.Binder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -747,6 +748,114 @@
     }
 
     @Test
+    public void addVendorCommandListener_receiveCallback_VendorCmdNoIdTest() {
+        int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+        int sourceAddress = Constants.ADDR_TV;
+        byte[] params = {0x00, 0x01, 0x02, 0x03};
+        int vendorId = 0x123456;
+
+        VendorCommandListener vendorCmdListener =
+                new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+        mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage vendorCommandNoId =
+                HdmiCecMessageBuilder.buildVendorCommand(sourceAddress, destAddress, params);
+        mNativeWrapper.onCecMessage(vendorCommandNoId);
+        mTestLooper.dispatchAll();
+        assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue();
+        assertThat(vendorCmdListener.mParamsCorrect).isTrue();
+        assertThat(vendorCmdListener.mHasVendorId).isFalse();
+    }
+
+    @Test
+    public void addVendorCommandListener_receiveCallback_VendorCmdWithIdTest() {
+        int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+        int sourceAddress = Constants.ADDR_TV;
+        byte[] params = {0x00, 0x01, 0x02, 0x03};
+        int vendorId = 0x123456;
+
+        VendorCommandListener vendorCmdListener =
+                new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+        mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage vendorCommandWithId =
+                HdmiCecMessageBuilder.buildVendorCommandWithId(
+                        sourceAddress, destAddress, vendorId, params);
+        mNativeWrapper.onCecMessage(vendorCommandWithId);
+        mTestLooper.dispatchAll();
+        assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue();
+        assertThat(vendorCmdListener.mParamsCorrect).isTrue();
+        assertThat(vendorCmdListener.mHasVendorId).isTrue();
+    }
+
+    @Test
+    public void addVendorCommandListener_noCallback_VendorCmdDiffIdTest() {
+        int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+        int sourceAddress = Constants.ADDR_TV;
+        byte[] params = {0x00, 0x01, 0x02, 0x03};
+        int vendorId = 0x123456;
+        int diffVendorId = 0x345678;
+
+        VendorCommandListener vendorCmdListener =
+                new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+        mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage vendorCommandWithDiffId =
+                HdmiCecMessageBuilder.buildVendorCommandWithId(
+                        sourceAddress, destAddress, diffVendorId, params);
+        mNativeWrapper.onCecMessage(vendorCommandWithDiffId);
+        mTestLooper.dispatchAll();
+        assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isFalse();
+    }
+
+    private static class VendorCommandListener extends IHdmiVendorCommandListener.Stub {
+        boolean mVendorCommandCallbackReceived = false;
+        boolean mParamsCorrect = false;
+        boolean mHasVendorId = false;
+
+        int mSourceAddress;
+        int mDestAddress;
+        byte[] mParams;
+        int mVendorId;
+
+        VendorCommandListener(int sourceAddress, int destAddress, byte[] params, int vendorId) {
+            this.mSourceAddress = sourceAddress;
+            this.mDestAddress = destAddress;
+            this.mParams = params.clone();
+            this.mVendorId = vendorId;
+        }
+
+        @Override
+        public void onReceived(
+                int sourceAddress, int destAddress, byte[] params, boolean hasVendorId) {
+            mVendorCommandCallbackReceived = true;
+            if (mSourceAddress == sourceAddress && mDestAddress == destAddress) {
+                byte[] expectedParams;
+                if (hasVendorId) {
+                    // If the command has vendor ID, we have to add it to mParams.
+                    expectedParams = new byte[params.length];
+                    expectedParams[0] = (byte) ((mVendorId >> 16) & 0xFF);
+                    expectedParams[1] = (byte) ((mVendorId >> 8) & 0xFF);
+                    expectedParams[2] = (byte) (mVendorId & 0xFF);
+                    System.arraycopy(mParams, 0, expectedParams, 3, mParams.length);
+                } else {
+                    expectedParams = params.clone();
+                }
+                if (Arrays.equals(expectedParams, params)) {
+                    mParamsCorrect = true;
+                }
+            }
+            mHasVendorId = hasVendorId;
+        }
+
+        @Override
+        public void onControlStateChanged(boolean enabled, int reason) {}
+    }
+
+    @Test
     public void dispatchMessageToLocalDevice_broadcastMessage_returnsHandled() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
                 Constants.ADDR_TV,
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 7f7c716..2f5993d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -61,7 +61,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.Map;
+import java.util.List;
 
 @SmallTest
 @Presubmit
@@ -136,9 +136,10 @@
         mApexManager.scanApexPackagesTraced(mPackageParser2,
                 ParallelPackageParser.makeExecutorService());
 
-        Map<String, String> services = mApexManager.getApexSystemServices();
+        List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
         assertThat(services).hasSize(1);
-        assertThat(services).containsKey("com.android.apex.test.ApexSystemService");
+        assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null))
+                .matches("com.android.apex.test.ApexSystemService");
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
index b621a44..869ac88 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
@@ -70,7 +70,7 @@
 import androidx.test.filters.Suppress;
 
 import com.android.frameworks.servicestests.R;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
@@ -106,11 +106,11 @@
 
     private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
 
-    private static final int APP_INSTALL_AUTO = PackageHelper.APP_INSTALL_AUTO;
+    private static final int APP_INSTALL_AUTO = InstallLocationUtils.APP_INSTALL_AUTO;
 
-    private static final int APP_INSTALL_DEVICE = PackageHelper.APP_INSTALL_INTERNAL;
+    private static final int APP_INSTALL_DEVICE = InstallLocationUtils.APP_INSTALL_INTERNAL;
 
-    private static final int APP_INSTALL_SDCARD = PackageHelper.APP_INSTALL_EXTERNAL;
+    private static final int APP_INSTALL_SDCARD = InstallLocationUtils.APP_INSTALL_EXTERNAL;
 
     void failStr(String errMsg) {
         Log.w(TAG, "errMsg=" + errMsg);
@@ -1214,7 +1214,7 @@
         int origDefaultLoc = getDefaultInstallLoc();
         InstallParams ip = null;
         try {
-            setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+            setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
             // Install first
             ip = installFromRawResource("install.apk", rawResId, installFlags, false,
                     false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1303,7 +1303,7 @@
         InstallParams ip = null;
         try {
             PackageManager pm = getPm();
-            setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+            setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
             // Install first
             ip = installFromRawResource("install.apk", R.raw.install, installFlags, false,
                     false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1517,11 +1517,11 @@
         int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
         boolean enable = getUserSettingSetInstallLocation();
         if (enable) {
-            if (userSetting == PackageHelper.APP_INSTALL_AUTO) {
+            if (userSetting == InstallLocationUtils.APP_INSTALL_AUTO) {
                 iloc = PackageInfo.INSTALL_LOCATION_AUTO;
-            } else if (userSetting == PackageHelper.APP_INSTALL_EXTERNAL) {
+            } else if (userSetting == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
                 iloc = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
-            } else if (userSetting == PackageHelper.APP_INSTALL_INTERNAL) {
+            } else if (userSetting == InstallLocationUtils.APP_INSTALL_INTERNAL) {
                 iloc = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
             }
         }
@@ -1552,7 +1552,7 @@
     }
     @LargeTest
     public void testExistingIUserI() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
         int iFlags = PackageManager.INSTALL_INTERNAL;
         setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
     }
@@ -1564,14 +1564,14 @@
             return;
         }
 
-        int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
         int iFlags = PackageManager.INSTALL_INTERNAL;
         setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
     }
 
     @LargeTest
     public void testExistingIUserA() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_AUTO;
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
         int iFlags = PackageManager.INSTALL_INTERNAL;
         setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
     }
@@ -1616,7 +1616,7 @@
     }
     @LargeTest
     public void testUserI() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
         int iloc = getExpectedInstallLocation(userSetting);
         setUserX(true, userSetting, iloc);
     }
@@ -1628,14 +1628,14 @@
             return;
         }
 
-        int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
         int iloc = getExpectedInstallLocation(userSetting);
         setUserX(true, userSetting, iloc);
     }
 
     @LargeTest
     public void testUserA() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_AUTO;
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
         int iloc = getExpectedInstallLocation(userSetting);
         setUserX(true, userSetting, iloc);
     }
@@ -1646,7 +1646,7 @@
      */
     @LargeTest
     public void testUserPrefOffUserI() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
         int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
         setUserX(false, userSetting, iloc);
     }
@@ -1658,14 +1658,14 @@
             return;
         }
 
-        int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
         int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
         setUserX(false, userSetting, iloc);
     }
 
     @LargeTest
     public void testUserPrefOffA() throws Exception {
-        int userSetting = PackageHelper.APP_INSTALL_AUTO;
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
         int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
         setUserX(false, userSetting, iloc);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 4a24bbd..8e53ca1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -17,7 +17,7 @@
 package com.android.server.pm;
 
 import static android.content.pm.SharedLibraryInfo.TYPE_DYNAMIC;
-import static android.content.pm.SharedLibraryInfo.TYPE_SDK;
+import static android.content.pm.SharedLibraryInfo.TYPE_SDK_PACKAGE;
 import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
 import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
 
@@ -258,7 +258,7 @@
         assertThat(scanResult.mSdkSharedLibraryInfo.getPackageName(), is("ogl.sdk_123"));
         assertThat(scanResult.mSdkSharedLibraryInfo.getName(), is("ogl.sdk"));
         assertThat(scanResult.mSdkSharedLibraryInfo.getLongVersion(), is(123L));
-        assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK_PACKAGE));
         assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getPackageName(),
                 is("ogl.sdk_123"));
         assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(),
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 51dbd97..827349a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -64,6 +64,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IWakeLockCallback;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerSaveState;
@@ -102,6 +103,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
@@ -479,21 +481,21 @@
         // First, ensure that a normal full wake lock does not cause a wakeup
         int flags = PowerManager.FULL_WAKE_LOCK;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
 
         // Ensure that the flag does *NOT* work with a partial wake lock.
         flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
 
         // Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK
         flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
     }
@@ -661,12 +663,12 @@
             wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]);
             return null;
         }).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(),
-                anyInt(), any(), any());
+                anyInt(), any(), any(), any());
         doAnswer(inv -> {
             wakelockMap.remove((String) inv.getArguments()[1]);
             return null;
         }).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(),
-                anyInt(), any(), any());
+                anyInt(), any(), any(), any());
 
         //
         // TEST STARTS HERE
@@ -679,7 +681,7 @@
 
         // Create a wakelock
         mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         assertThat(wakelockMap.get(tag)).isEqualTo(flags);  // Verify wakelock is active.
 
         // Confirm that the wakelocks have been disabled when the forceSuspend is in flight.
@@ -737,7 +739,7 @@
         // Take a nap and verify we no longer hold the blocker
         int flags = PowerManager.DOZE_WAKE_LOCK;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
         when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
         mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
@@ -893,7 +895,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         advanceTime(60);
@@ -919,7 +921,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
 
         advanceTime(1500);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
@@ -995,7 +997,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -1035,7 +1037,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -1076,7 +1078,7 @@
 
         mService.getBinderServiceInstance().acquireWakeLock(token,
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, nonDefaultDisplay);
+                null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
 
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_AWAKE);
@@ -1640,7 +1642,65 @@
         IBinder token = new Binder();
         String packageName = "pkg.name";
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
         return mService.findWakeLockLocked(token);
     }
+
+    /**
+     * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback.
+     */
+    @Test
+    public void testNotifyWakeLockCallback() {
+        createService();
+        startSystem();
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final IWakeLockCallback callback = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder = Mockito.mock(Binder.class);
+        when(callback.asBinder()).thenReturn(callbackBinder);
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback);
+        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback));
+
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback));
+    }
+
+    /**
+     * Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback.
+     */
+    @Test
+    public void testNotifyWakeLockCallbackChange() {
+        createService();
+        startSystem();
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder1 = Mockito.mock(Binder.class);
+        when(callback1.asBinder()).thenReturn(callbackBinder1);
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1);
+        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback1));
+
+        final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder2 = Mockito.mock(Binder.class);
+        when(callback2.asBinder()).thenReturn(callbackBinder2);
+        mService.getBinderServiceInstance().updateWakeLockCallback(token, callback2);
+        verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName),
+                anyInt(), anyInt(), any(), any(), same(callback1),
+                anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), any(), any(),
+                same(callback2));
+
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback2));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 5eed30b..91d4f8f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -67,6 +67,7 @@
     @Mock Vibrator mVibrator;
 
     private final String callPkg = "com.android.server.notification";
+    private final String sysPkg = "android";
     private final int callUid = 10;
     private String smsPkg;
     private final int smsUid = 11;
@@ -79,6 +80,7 @@
     private NotificationRecord mRecordHighCall;
     private NotificationRecord mRecordHighCallStyle;
     private NotificationRecord mRecordEmail;
+    private NotificationRecord mRecordSystemMax;
     private NotificationRecord mRecordInlineReply;
     private NotificationRecord mRecordSms;
     private NotificationRecord mRecordStarredContact;
@@ -191,6 +193,12 @@
         mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
         mRecordContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
 
+        Notification nSystemMax = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
+        mRecordSystemMax = new NotificationRecord(mContext, new StatusBarNotification(sysPkg,
+                sysPkg, 1, "systemmax", uid2, uid2, nSystemMax, new UserHandle(userId),
+                "", 1244), getDefaultChannel());
+        mRecordSystemMax.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
+
         Notification n8 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
         mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
                 pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
@@ -267,6 +275,7 @@
         }
         expected.add(mRecordStarredContact);
         expected.add(mRecordContact);
+        expected.add(mRecordSystemMax);
         expected.add(mRecordEmail);
         expected.add(mRecordUrgent);
         expected.add(mNoMediaSessionMedia);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 7542033..7e27e54 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -66,6 +66,7 @@
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
 import android.widget.RemoteViews;
 
 import androidx.test.filters.SmallTest;
@@ -1304,4 +1305,45 @@
 
         assertFalse(record.isConversation());
     }
+
+    @Test
+    public void mergePhoneNumbers_nulls() {
+        // make sure nothing dies if we just don't have any phone numbers
+        StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, null /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
+
+        // by default, no phone numbers
+        assertNull(record.getPhoneNumbers());
+
+        // nothing happens if we attempt to merge phone numbers but there aren't any
+        record.mergePhoneNumbers(null);
+        assertNull(record.getPhoneNumbers());
+    }
+
+    @Test
+    public void mergePhoneNumbers_addNumbers() {
+        StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, null /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
+
+        // by default, no phone numbers
+        assertNull(record.getPhoneNumbers());
+
+        // make sure it behaves properly when we merge in some real content
+        record.mergePhoneNumbers(new ArraySet<>(
+                new String[]{"16175551212", "16175552121"}));
+        assertTrue(record.getPhoneNumbers().contains("16175551212"));
+        assertTrue(record.getPhoneNumbers().contains("16175552121"));
+        assertFalse(record.getPhoneNumbers().contains("16175553434"));
+
+        // now merge in a new number, make sure old ones are still there and the new one
+        // is also there
+        record.mergePhoneNumbers(new ArraySet<>(new String[]{"16175553434"}));
+        assertTrue(record.getPhoneNumbers().contains("16175551212"));
+        assertTrue(record.getPhoneNumbers().contains("16175552121"));
+        assertTrue(record.getPhoneNumbers().contains("16175553434"));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index fa294dd..3b67182 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -20,8 +20,8 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.permission.PermissionManager.PERMISSION_GRANTED;
-import static android.permission.PermissionManager.PERMISSION_SOFT_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -130,13 +130,13 @@
 
     @Test
     public void testHasPermission() throws Exception {
-        when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
+        when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
 
         assertThat(mPermissionHelper.hasPermission(1)).isTrue();
 
-        when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
-                .thenReturn(PERMISSION_SOFT_DENIED);
+        when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
+                .thenReturn(PERMISSION_DENIED);
 
         assertThat(mPermissionHelper.hasPermission(1)).isFalse();
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
index 0bf105d..0552a83 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
@@ -19,8 +19,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
 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;
 
@@ -29,6 +34,7 @@
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserManager;
@@ -43,6 +49,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -240,6 +248,118 @@
         assertFalse(ContentProvider.uriHasUserId(queryUri.getValue()));
     }
 
+    @Test
+    public void testMergePhoneNumbers_noPhoneNumber() {
+        // If merge phone number is called but the contacts lookup turned up no available
+        // phone number (HAS_PHONE_NUMBER is false), then no query should happen.
+
+        // setup of various bits required for querying
+        final Context mockContext = mock(Context.class);
+        final ContentResolver mockContentResolver = mock(ContentResolver.class);
+        when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+        final int contactId = 12345;
+        final Uri lookupUri = Uri.withAppendedPath(
+                ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
+
+        // when the contact is looked up, we return a cursor that has one entry whose info is:
+        //  _ID: 1
+        //  LOOKUP_KEY: "testlookupkey"
+        //  STARRED: 0
+        //  HAS_PHONE_NUMBER: 0
+        Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 0);
+        when(mockContentResolver.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+        // call searchContacts and then mergePhoneNumbers, make sure we never actually
+        // query the content resolver for a phone number
+        new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+        verify(mockContentResolver, never()).query(
+                eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
+                eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
+                contains(ContactsContract.Contacts.LOOKUP_KEY),
+                any(),  // selection args
+                isNull());  // sort order
+    }
+
+    @Test
+    public void testMergePhoneNumbers_hasNumber() {
+        // If merge phone number is called and the contact lookup has a phone number,
+        // make sure there's then a subsequent query for the phone number.
+
+        // setup of various bits required for querying
+        final Context mockContext = mock(Context.class);
+        final ContentResolver mockContentResolver = mock(ContentResolver.class);
+        when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+        final int contactId = 12345;
+        final Uri lookupUri = Uri.withAppendedPath(
+                ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
+
+        // when the contact is looked up, we return a cursor that has one entry whose info is:
+        //  _ID: 1
+        //  LOOKUP_KEY: "testlookupkey"
+        //  STARRED: 0
+        //  HAS_PHONE_NUMBER: 1
+        Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 1);
+
+        // make sure to add some specifics so this cursor is only returned for the
+        // contacts database lookup.
+        when(mockContentResolver.query(eq(lookupUri), any(),
+                isNull(), isNull(), isNull())).thenReturn(cursor);
+
+        // in the case of a phone lookup, return null cursor; that's not an error case
+        // and we're not checking the actual storing of the phone data here.
+        when(mockContentResolver.query(eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
+                eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
+                contains(ContactsContract.Contacts.LOOKUP_KEY),
+                any(), isNull())).thenReturn(null);
+
+        // call searchContacts and then mergePhoneNumbers, and check that we query
+        // once for the
+        new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+        verify(mockContentResolver, times(1)).query(
+                eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
+                eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
+                contains(ContactsContract.Contacts.LOOKUP_KEY),
+                eq(new String[] { "testlookupkey" }),  // selection args
+                isNull());  // sort order
+    }
+
+    // Creates a cursor that points to one item of Contacts data with the specified
+    // columns.
+    private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) {
+        Cursor mockCursor = mock(Cursor.class);
+        when(mockCursor.moveToFirst()).thenReturn(true);
+        doAnswer(new Answer<Boolean>() {
+            boolean mAccessed = false;
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                if (!mAccessed) {
+                    mAccessed = true;
+                    return true;
+                }
+                return false;
+            }
+
+        }).when(mockCursor).moveToNext();
+
+        // id
+        when(mockCursor.getColumnIndex(ContactsContract.Contacts._ID)).thenReturn(0);
+        when(mockCursor.getInt(0)).thenReturn(id);
+
+        // lookup key
+        when(mockCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)).thenReturn(1);
+        when(mockCursor.getString(1)).thenReturn(lookupKey);
+
+        // starred
+        when(mockCursor.getColumnIndex(ContactsContract.Contacts.STARRED)).thenReturn(2);
+        when(mockCursor.getInt(2)).thenReturn(starred);
+
+        // has phone number
+        when(mockCursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)).thenReturn(3);
+        when(mockCursor.getInt(3)).thenReturn(hasPhone);
+
+        return mockCursor;
+    }
+
     private void assertStringArrayEquals(String message, String[] expected, String[] result) {
         String expectedString = Arrays.toString(expected);
         String resultString = Arrays.toString(result);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index fb15088..abcc8c1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -17,7 +17,6 @@
 package com.android.server.notification;
 
 import static android.app.Notification.CATEGORY_CALL;
-import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
@@ -25,6 +24,7 @@
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
@@ -43,16 +43,20 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager.Policy;
 import android.media.AudioAttributes;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
+import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.ArraySet;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.server.UiServiceTestCase;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -68,10 +72,24 @@
     private NotificationMessagingUtil mMessagingUtil;
     private ZenModeFiltering mZenModeFiltering;
 
+    @Mock private TelephonyManager mTelephonyManager;
+
+    private long mTestStartTime;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mZenModeFiltering = new ZenModeFiltering(mContext, mMessagingUtil);
+
+        // for repeat callers / matchesCallFilter
+        mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
+        mTestStartTime = System.currentTimeMillis();
+    }
+
+    @After
+    public void tearDown() {
+        // make sure to get rid of any data stored in repeat callers
+        mZenModeFiltering.cleanUpCallersAfter(mTestStartTime);
     }
 
     private NotificationRecord getNotificationRecord() {
@@ -95,6 +113,27 @@
         return r;
     }
 
+    private Bundle makeExtrasBundleWithPeople(String[] people) {
+        Bundle extras = new Bundle();
+        extras.putObject(Notification.EXTRA_PEOPLE_LIST, people);
+        return extras;
+    }
+
+    // Create a notification record with the people String array as the
+    // bundled extras, and the numbers ArraySet as additional phone numbers.
+    private NotificationRecord getRecordWithPeopleInfo(String[] people,
+            ArraySet<String> numbers) {
+        // set up notification record
+        NotificationRecord r = mock(NotificationRecord.class);
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        Notification notification = mock(Notification.class);
+        notification.extras = makeExtrasBundleWithPeople(people);
+        when(sbn.getNotification()).thenReturn(notification);
+        when(r.getSbn()).thenReturn(sbn);
+        when(r.getPhoneNumbers()).thenReturn(numbers);
+        return r;
+    }
+
     @Test
     public void testIsMessage() {
         NotificationRecord r = getNotificationRecord();
@@ -309,4 +348,150 @@
 
         assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
     }
+
+    @Test
+    public void testMatchesCallFilter_repeatCallers_directMatch() {
+        // after calls given an email with an exact string match, make sure that
+        // matchesCallFilter returns the right thing
+        String[] mailSource = new String[]{"mailto:hello.world"};
+        mZenModeFiltering.recordCall(getRecordWithPeopleInfo(mailSource, null));
+
+        // set up policy to only allow repeat callers
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        // check whether matchesCallFilter returns the right thing
+        Bundle inputMatches = makeExtrasBundleWithPeople(new String[]{"mailto:hello.world"});
+        Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"});
+        assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                inputMatches, null, 0, 0));
+        assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                inputWrong, null, 0, 0));
+    }
+
+    @Test
+    public void testMatchesCallFilter_repeatCallers_telephoneVariants() {
+        // set up telephony manager behavior
+        when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+        String[] telSource = new String[]{"tel:+1-617-555-1212"};
+        mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null));
+
+        // set up policy to only allow repeat callers
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        // cases to test:
+        //   - identical number
+        //   - same number, different formatting
+        //   - different number
+        //   - garbage
+        Bundle identical = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+        Bundle same = makeExtrasBundleWithPeople(new String[]{"tel:16175551212"});
+        Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:123-456-7890"});
+        Bundle garbage = makeExtrasBundleWithPeople(new String[]{"asdfghjkl;"});
+
+        assertTrue("identical numbers should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                identical, null, 0, 0));
+        assertTrue("equivalent but non-identical numbers should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                same, null, 0, 0));
+        assertFalse("non-equivalent numbers should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                different, null, 0, 0));
+        assertFalse("non-tel strings should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                policy, UserHandle.SYSTEM,
+                garbage, null, 0, 0));
+    }
+
+    @Test
+    public void testMatchesCallFilter_repeatCallers_urlEncodedTels() {
+        // this is not intended to be a supported case but is one that we have seen
+        // sometimes in the wild, so make sure we handle url-encoded telephone numbers correctly
+        // when somebody provides one.
+
+        // set up telephony manager behavior
+        when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+        String[] telSource = new String[]{"tel:%2B16175551212"};
+        mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null));
+
+        // set up policy to only allow repeat callers
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        // test cases for various forms of the same phone number and different ones
+        Bundle same1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+        Bundle same2 = makeExtrasBundleWithPeople(new String[]{"tel:%2B1-617-555-1212"});
+        Bundle same3 = makeExtrasBundleWithPeople(new String[]{"tel:6175551212"});
+        Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"});
+        Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"});
+
+        assertTrue("same number 1 should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        same1, null, 0, 0));
+        assertTrue("same number 2 should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        same2, null, 0, 0));
+        assertTrue("same number 3 should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        same3, null, 0, 0));
+        assertFalse("different number 1 should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        different1, null, 0, 0));
+        assertFalse("different number 2 should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        different2, null, 0, 0));
+    }
+
+    @Test
+    public void testMatchesCallFilter_repeatCallers_viaRecordPhoneNumbers() {
+        // make sure that phone numbers that are passed in via the NotificationRecord's
+        // cached phone numbers field (from a contact lookup if the record is provided a contact
+        // uri) also get recorded in the repeat callers list.
+
+        // set up telephony manager behavior
+        when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+        String[] contactSource = new String[]{"content://contacts/lookup/uri-here"};
+        ArraySet<String> contactNumbers = new ArraySet<>(
+                new String[]{"1-617-555-1212", "1-617-555-3434"});
+        NotificationRecord record = getRecordWithPeopleInfo(contactSource, contactNumbers);
+        record.mergePhoneNumbers(contactNumbers);
+        mZenModeFiltering.recordCall(record);
+
+        // set up policy to only allow repeat callers
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        // both phone numbers should register here
+        Bundle tel1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+        Bundle tel2 = makeExtrasBundleWithPeople(new String[]{"tel:16175553434"});
+        Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:16175555656"});
+
+        assertTrue("contact number 1 should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        tel1, null, 0, 0));
+        assertTrue("contact number 2 should match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        tel2, null, 0, 0));
+        assertFalse("different number should not match",
+                ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                        policy, UserHandle.SYSTEM,
+                        different, null, 0, 0));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 30ad1f9..3298d11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -85,7 +85,6 @@
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
@@ -3114,7 +3113,7 @@
     }
 
     @Test
-    public void testInClosingAnimation_doNotHideSurface() {
+    public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         makeWindowVisibleAndDrawn(app);
 
@@ -3123,16 +3122,45 @@
         mDisplayContent.mClosingApps.add(app.mActivityRecord);
         mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
 
-        // Update visibility and call to remove window
-        app.mActivityRecord.commitVisibility(false, false);
+        // Remove window during transition, so it is requested to hide, but won't be committed until
+        // the transition is finished.
+        app.mActivityRecord.onRemovedFromDisplay();
+
+        assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord));
+        assertFalse(app.mActivityRecord.isVisibleRequested());
+        assertTrue(app.mActivityRecord.isVisible());
+        assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+        // Start transition.
         app.mActivityRecord.prepareSurfaces();
 
         // Because the app is waiting for transition, it should not hide the surface.
         assertTrue(app.mActivityRecord.isSurfaceShowing());
+    }
 
-        // Ensure onAnimationFinished will callback when the closing animation is finished.
-        verify(app.mActivityRecord).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
-                eq(null));
+    @Test
+    public void testInClosingAnimation_visibilityCommitted_hideSurface() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        makeWindowVisibleAndDrawn(app);
+
+        // Put the activity in close transition.
+        mDisplayContent.mOpeningApps.clear();
+        mDisplayContent.mClosingApps.add(app.mActivityRecord);
+        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+
+        // Commit visibility before start transition.
+        app.mActivityRecord.commitVisibility(false, false);
+
+        assertFalse(app.mActivityRecord.isVisibleRequested());
+        assertFalse(app.mActivityRecord.isVisible());
+        assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+        // Start transition.
+        app.mActivityRecord.prepareSurfaces();
+
+        // Because the app visibility has been committed before the transition start, it should hide
+        // the surface.
+        assertFalse(app.mActivityRecord.isSurfaceShowing());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 7c340ec..8ada971 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -191,7 +191,8 @@
             public void onFixedRotationFinished(int displayId) {}
 
             @Override
-            public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
+            public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+                    List<Rect> unrestricted) {}
         };
         int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
         for (int i = 0; i < displayIds.length; i++) {
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index 24fda17..27e8d69 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -16,6 +16,8 @@
 
 package com.android.server.usage;
 
+import static android.app.ActivityManager.procStateToString;
+
 import static com.android.server.usage.UsageStatsService.DEBUG_RESPONSE_STATS;
 
 import android.annotation.ElapsedRealtimeLong;
@@ -23,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
 import android.app.usage.BroadcastResponseStats;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -69,15 +72,26 @@
     private SparseArray<SparseArray<UserBroadcastResponseStats>> mUserResponseStats =
             new SparseArray<>();
 
+    private AppStandbyInternal mAppStandby;
+
+    BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) {
+        mAppStandby = appStandby;
+    }
+
     // TODO (206518114): Move all callbacks handling to a handler thread.
     void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
             UserHandle targetUser, long idForResponseEvent,
-            @ElapsedRealtimeLong long timestampMs) {
+            @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) {
         if (DEBUG_RESPONSE_STATS) {
-            Slog.d(TAG, TextUtils.formatSimple(
-                    "reportBroadcastDispatchEvent; srcUid=%d, tgtPkg=%s, tgtUsr=%d, id=%d, ts=%s",
+            Slog.d(TAG, TextUtils.formatSimple("reportBroadcastDispatchEvent; "
+                            + "srcUid=%d, tgtPkg=%s, tgtUsr=%d, id=%d, ts=%s, state=%s",
                     sourceUid, targetPackage, targetUser, idForResponseEvent,
-                    TimeUtils.formatDuration(timestampMs)));
+                    TimeUtils.formatDuration(timestampMs), procStateToString(targetUidProcState)));
+        }
+        if (targetUidProcState <= mAppStandby.getBroadcastResponseFgThresholdState()) {
+            // No need to track the broadcast response state while the target app is
+            // in the foreground.
+            return;
         }
         synchronized (mLock) {
             final LongSparseArray<BroadcastEvent> broadcastEvents =
@@ -132,8 +146,7 @@
                 if (dispatchTimestampMs >= timestampMs) {
                     continue;
                 }
-                // TODO (206518114): Make the constant configurable.
-                if (elapsedDurationMs <= 2 * 60 * 1000) {
+                if (elapsedDurationMs <= mAppStandby.getBroadcastResponseWindowDurationMs()) {
                     final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i);
                     final BroadcastResponseStats responseStats =
                             getBroadcastResponseStats(broadcastEvent);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index e90d28a..98a41bc 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -42,6 +42,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessState;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
 import android.app.PendingIntent;
@@ -281,7 +282,7 @@
         mHandler = new H(BackgroundThread.get().getLooper());
 
         mAppStandby = mInjector.getAppStandbyController(getContext());
-        mResponseStatsTracker = new BroadcastResponseStatsTracker();
+        mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby);
 
         mAppTimeLimit = new AppTimeLimitController(getContext(),
                 new AppTimeLimitController.TimeLimitCallbackListener() {
@@ -3042,9 +3043,9 @@
         @Override
         public void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
                 @NonNull UserHandle targetUser, long idForResponseEvent,
-                @ElapsedRealtimeLong long timestampMs) {
+                @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) {
             mResponseStatsTracker.reportBroadcastDispatchEvent(sourceUid, targetPackage,
-                    targetUser, idForResponseEvent, timestampMs);
+                    targetUser, idForResponseEvent, timestampMs, targetUidProcState);
         }
 
         @Override
diff --git a/services/wallpapereffectsgeneration/Android.bp b/services/wallpapereffectsgeneration/Android.bp
new file mode 100644
index 0000000..4dbb0fd
--- /dev/null
+++ b/services/wallpapereffectsgeneration/Android.bp
@@ -0,0 +1,22 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "services.wallpapereffectsgeneration-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.wallpapereffectsgeneration",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.wallpapereffectsgeneration-sources"],
+    libs: ["services.core"],
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
new file mode 100644
index 0000000..c228daf
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.wallpapereffectsgeneration.IWallpaperEffectsGenerationService;
+import android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the
+ * {@link android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService}
+ * implementation in another process.
+ */
+public class RemoteWallpaperEffectsGenerationService extends
+        AbstractMultiplePendingRequestsRemoteService<RemoteWallpaperEffectsGenerationService,
+                IWallpaperEffectsGenerationService> {
+
+    private static final String TAG =
+            RemoteWallpaperEffectsGenerationService.class.getSimpleName();
+
+    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+    private final RemoteWallpaperEffectsGenerationServiceCallback mCallback;
+
+    public RemoteWallpaperEffectsGenerationService(Context context,
+            ComponentName componentName, int userId,
+            RemoteWallpaperEffectsGenerationServiceCallback callback,
+            boolean bindInstantServiceAllowed,
+            boolean verbose) {
+        super(context, WallpaperEffectsGenerationService.SERVICE_INTERFACE,
+                componentName, userId, callback,
+                context.getMainThreadHandler(),
+                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+                verbose, /* initialCapacity= */ 1);
+        mCallback = callback;
+    }
+
+    @Override
+    protected IWallpaperEffectsGenerationService getServiceInterface(IBinder service) {
+        return IWallpaperEffectsGenerationService.Stub.asInterface(service);
+    }
+
+    @Override
+    protected long getTimeoutIdleBindMillis() {
+        return PERMANENT_BOUND_TIMEOUT_MS;
+    }
+
+    @Override
+    protected long getRemoteRequestMillis() {
+        return TIMEOUT_REMOTE_REQUEST_MILLIS;
+    }
+
+    /**
+     * Schedules a request to bind to the remote service.
+     */
+    public void reconnect() {
+        super.scheduleBind();
+    }
+
+    /**
+     * Schedule async request on remote service.
+     */
+    public void scheduleOnResolvedService(
+            @NonNull AsyncRequest<IWallpaperEffectsGenerationService> request) {
+        scheduleAsyncRequest(request);
+    }
+
+    /**
+     * Execute async request on remote service immediately instead of sending it to Handler queue.
+     */
+    public void executeOnResolvedService(
+            @NonNull AsyncRequest<IWallpaperEffectsGenerationService> request) {
+        executeAsyncRequest(request);
+    }
+
+    /**
+     * Notifies server (WallpaperEffectsGenerationPerUserService) about unexpected events..
+     */
+    public interface RemoteWallpaperEffectsGenerationServiceCallback
+            extends VultureCallback<RemoteWallpaperEffectsGenerationService> {
+        /**
+         * Notifies change in connected state of the remote service.
+         */
+        void onConnectedStateChanged(boolean connected);
+    }
+
+    @Override // from AbstractRemoteService
+    protected void handleOnConnectedStateChanged(boolean connected) {
+        if (mCallback != null) {
+            mCallback.onConnectedStateChanged(connected);
+        }
+    }
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
new file mode 100644
index 0000000..0d0b3e0
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpapereffectsgeneration;
+
+import static android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.WALLPAPER_EFFECTS_GENERATION_SERVICE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to return wallpaper effect given a request.
+ */
+public class WallpaperEffectsGenerationManagerService extends
+        AbstractMasterSystemService<WallpaperEffectsGenerationManagerService,
+                WallpaperEffectsGenerationPerUserService> {
+    private static final String TAG =
+            WallpaperEffectsGenerationManagerService.class.getSimpleName();
+    private static final boolean DEBUG = false;
+    private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+    public WallpaperEffectsGenerationManagerService(Context context) {
+        super(context,
+                new FrameworkResourcesServiceNameResolver(context,
+                        com.android.internal.R.string.config_defaultWallpaperEffectsGenerationService),
+                null,
+                PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+    }
+
+    @Override
+    protected WallpaperEffectsGenerationPerUserService newServiceLocked(int resolvedUserId,
+            boolean disabled) {
+        return new WallpaperEffectsGenerationPerUserService(this, mLock, resolvedUserId);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(WALLPAPER_EFFECTS_GENERATION_SERVICE,
+                new WallpaperEffectsGenerationManagerStub());
+    }
+
+    @Override
+    protected void enforceCallingPermissionForManagement() {
+        getContext().enforceCallingPermission(MANAGE_WALLPAPER_EFFECTS_GENERATION, TAG);
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+        final WallpaperEffectsGenerationPerUserService service = peekServiceForUserLocked(userId);
+        if (service != null) {
+            service.onPackageUpdatedLocked();
+        }
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+        final WallpaperEffectsGenerationPerUserService service = peekServiceForUserLocked(userId);
+        if (service != null) {
+            service.onPackageRestartedLocked();
+        }
+    }
+
+    @Override
+    protected int getMaximumTemporaryServiceDurationMs() {
+        return MAX_TEMP_SERVICE_DURATION_MS;
+    }
+
+    private class WallpaperEffectsGenerationManagerStub
+            extends IWallpaperEffectsGenerationManager.Stub {
+        @Override
+        public void generateCinematicEffect(@NonNull CinematicEffectRequest request,
+                @NonNull ICinematicEffectListener listener) {
+            if (!runForUserLocked("generateCinematicEffect", (service) ->
+                    service.onGenerateCinematicEffectLocked(request, listener))) {
+                try {
+                    listener.onCinematicEffectGenerated(
+                            new CinematicEffectResponse.Builder(
+                                    CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_ERROR,
+                                    request.getTaskId()).build());
+                } catch (RemoteException e) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "fail to invoke cinematic effect listener for task["
+                                + request.getTaskId() + "]");
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void returnCinematicEffectResponse(@NonNull CinematicEffectResponse response) {
+            runForUserLocked("returnCinematicResponse", (service) ->
+                    service.onReturnCinematicEffectResponseLocked(response));
+        }
+
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err,
+                @NonNull String[] args, @Nullable ShellCallback callback,
+                @NonNull ResultReceiver resultReceiver) {
+            new WallpaperEffectsGenerationManagerServiceShellCommand(
+                    WallpaperEffectsGenerationManagerService.this)
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+
+        /**
+         * Execute the operation for the user locked. Return true if
+         * WallpaperEffectsGenerationPerUserService is found for the user.
+         * Otherwise return false.
+         */
+        private boolean runForUserLocked(@NonNull final String func,
+                @NonNull final Consumer<WallpaperEffectsGenerationPerUserService> c) {
+            ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+            final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                    Binder.getCallingUserHandle().getIdentifier(), false, ALLOW_NON_FULL,
+                    null, null);
+            if (DEBUG) {
+                Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid());
+            }
+            Context ctx = getContext();
+            if (!(ctx.checkCallingPermission(MANAGE_WALLPAPER_EFFECTS_GENERATION)
+                    == PERMISSION_GRANTED
+                    || mServiceNameResolver.isTemporary(userId)
+                    || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+                String msg = "Permission Denial: Cannot call " + func + " from pid="
+                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+            final long origId = Binder.clearCallingIdentity();
+            boolean accepted = false;
+            try {
+                synchronized (mLock) {
+                    final WallpaperEffectsGenerationPerUserService service =
+                            getServiceForUserLocked(userId);
+                    if (service != null) {
+                        accepted = true;
+                        c.accept(service);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+
+            return accepted;
+        }
+    }
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java
new file mode 100644
index 0000000..fc6f75f
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the WallpaperEffectsGenerationService.
+ */
+public class WallpaperEffectsGenerationManagerServiceShellCommand extends ShellCommand {
+
+    private static final String TAG =
+            WallpaperEffectsGenerationManagerServiceShellCommand.class.getSimpleName();
+
+    private final WallpaperEffectsGenerationManagerService mService;
+
+    public WallpaperEffectsGenerationManagerServiceShellCommand(
+            @NonNull WallpaperEffectsGenerationManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch (cmd) {
+            case "set": {
+                final String what = getNextArgRequired();
+                switch (what) {
+                    case "temporary-service": {
+                        final int userId = Integer.parseInt(getNextArgRequired());
+                        String serviceName = getNextArg();
+                        if (serviceName == null) {
+                            mService.resetTemporaryService(userId);
+                            pw.println("WallpaperEffectsGenerationService temporarily reset. ");
+                            return 0;
+                        }
+                        final int duration = Integer.parseInt(getNextArgRequired());
+                        mService.setTemporaryService(userId, serviceName, duration);
+                        pw.println("WallpaperEffectsGenerationService temporarily set to "
+                                + serviceName + " for " + duration + "ms");
+                        break;
+                    }
+                }
+            }
+            break;
+            default:
+                return handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        try (PrintWriter pw = getOutPrintWriter()) {
+            pw.println("WallpaperEffectsGenerationService commands:");
+            pw.println("  help");
+            pw.println("    Prints this help text.");
+            pw.println("");
+            pw.println("  set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+            pw.println("    Temporarily (for DURATION ms) changes the service implemtation.");
+            pw.println("    To reset, call with just the USER_ID argument.");
+            pw.println("");
+        }
+    }
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
new file mode 100644
index 0000000..d541051
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wallpapereffectsgeneration;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link WallpaperEffectsGenerationManagerService}.
+ */
+public class WallpaperEffectsGenerationPerUserService extends
+        AbstractPerUserSystemService<WallpaperEffectsGenerationPerUserService,
+                WallpaperEffectsGenerationManagerService> implements
+        RemoteWallpaperEffectsGenerationService.RemoteWallpaperEffectsGenerationServiceCallback {
+
+    private static final String TAG =
+            WallpaperEffectsGenerationPerUserService.class.getSimpleName();
+
+    @GuardedBy("mLock")
+    private CinematicEffectListenerWrapper mCinematicEffectListenerWrapper;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private RemoteWallpaperEffectsGenerationService mRemoteService;
+
+    protected WallpaperEffectsGenerationPerUserService(
+            WallpaperEffectsGenerationManagerService master,
+            Object lock, int userId) {
+        super(master, lock, userId);
+    }
+
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws NameNotFoundException {
+        ServiceInfo si;
+        try {
+            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new NameNotFoundException("Could not get service for " + serviceComponent);
+        }
+        if (!Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE.equals(si.permission)) {
+            Slog.w(TAG, "WallpaperEffectsGenerationService from '" + si.packageName
+                    + "' does not require permission "
+                    + Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE);
+            throw new SecurityException("Service does not require permission "
+                    + Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE);
+        }
+        return si;
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        updateRemoteServiceLocked();
+        return enabledChanged;
+    }
+
+    /**
+     * Notifies the service of a new cinematic effect generation request.
+     */
+    @GuardedBy("mLock")
+    public void onGenerateCinematicEffectLocked(
+            @NonNull CinematicEffectRequest cinematicEffectRequest,
+            @NonNull ICinematicEffectListener cinematicEffectListener) {
+        String newTaskId = cinematicEffectRequest.getTaskId();
+        // Previous request is still being processed.
+        if (mCinematicEffectListenerWrapper != null) {
+            if (mCinematicEffectListenerWrapper.mTaskId.equals(newTaskId)) {
+                invokeCinematicListenerAndCleanup(
+                        new CinematicEffectResponse.Builder(
+                                CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_PENDING, newTaskId)
+                                .build()
+                );
+            } else {
+                invokeCinematicListenerAndCleanup(
+                        new CinematicEffectResponse.Builder(
+                                CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS,
+                                newTaskId).build()
+                );
+            }
+            return;
+        }
+        RemoteWallpaperEffectsGenerationService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.executeOnResolvedService(
+                    s -> s.onGenerateCinematicEffect(cinematicEffectRequest));
+            mCinematicEffectListenerWrapper =
+                    new CinematicEffectListenerWrapper(newTaskId, cinematicEffectListener);
+        } else {
+            if (isDebug()) {
+                Slog.d(TAG, "Remote service not found");
+            }
+            try {
+                cinematicEffectListener.onCinematicEffectGenerated(
+                        createErrorCinematicEffectResponse(newTaskId));
+            } catch (RemoteException e) {
+                if (isDebug()) {
+                    Slog.d(TAG, "Failed to invoke cinematic effect listener for task [" + newTaskId
+                            + "]");
+                }
+            }
+        }
+    }
+
+    /**
+     * Notifies the service of a generated cinematic effect response.
+     */
+    @GuardedBy("mLock")
+    public void onReturnCinematicEffectResponseLocked(
+            @NonNull CinematicEffectResponse cinematicEffectResponse) {
+        invokeCinematicListenerAndCleanup(cinematicEffectResponse);
+    }
+
+    @GuardedBy("mLock")
+    private void updateRemoteServiceLocked() {
+        if (mRemoteService != null) {
+            mRemoteService.destroy();
+            mRemoteService = null;
+        }
+        // End existing response and clean up listener for next request.
+        if (mCinematicEffectListenerWrapper != null) {
+            invokeCinematicListenerAndCleanup(
+                    createErrorCinematicEffectResponse(mCinematicEffectListenerWrapper.mTaskId));
+        }
+
+    }
+
+    void onPackageUpdatedLocked() {
+        if (isDebug()) {
+            Slog.v(TAG, "onPackageUpdatedLocked()");
+        }
+        destroyAndRebindRemoteService();
+    }
+
+    void onPackageRestartedLocked() {
+        if (isDebug()) {
+            Slog.v(TAG, "onPackageRestartedLocked()");
+        }
+        destroyAndRebindRemoteService();
+    }
+
+    private void destroyAndRebindRemoteService() {
+        if (mRemoteService == null) {
+            return;
+        }
+
+        if (isDebug()) {
+            Slog.d(TAG, "Destroying the old remote service.");
+        }
+        mRemoteService.destroy();
+        mRemoteService = null;
+        mRemoteService = ensureRemoteServiceLocked();
+        if (mRemoteService != null) {
+            if (isDebug()) {
+                Slog.d(TAG, "Rebinding to the new remote service.");
+            }
+            mRemoteService.reconnect();
+        }
+        // Clean up listener for next request.
+        if (mCinematicEffectListenerWrapper != null) {
+            invokeCinematicListenerAndCleanup(
+                    createErrorCinematicEffectResponse(mCinematicEffectListenerWrapper.mTaskId));
+        }
+    }
+
+    private CinematicEffectResponse createErrorCinematicEffectResponse(String taskId) {
+        return new CinematicEffectResponse.Builder(
+                CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_ERROR,
+                taskId).build();
+    }
+
+    @GuardedBy("mLock")
+    private void invokeCinematicListenerAndCleanup(
+            CinematicEffectResponse cinematicEffectResponse) {
+        try {
+            if (mCinematicEffectListenerWrapper != null
+                    && mCinematicEffectListenerWrapper.mListener != null) {
+                mCinematicEffectListenerWrapper.mListener.onCinematicEffectGenerated(
+                        cinematicEffectResponse);
+            } else {
+                if (isDebug()) {
+                    Slog.w(TAG, "Cinematic effect listener not found for task["
+                            + mCinematicEffectListenerWrapper.mTaskId + "]");
+                }
+            }
+        } catch (RemoteException e) {
+            if (isDebug()) {
+                Slog.w(TAG, "Error invoking cinematic effect listener for task["
+                        + mCinematicEffectListenerWrapper.mTaskId + "]");
+            }
+        } finally {
+            mCinematicEffectListenerWrapper = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteWallpaperEffectsGenerationService ensureRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            final String serviceName = getComponentNameLocked();
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "ensureRemoteServiceLocked(): not set");
+                }
+                return null;
+            }
+            ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+            mRemoteService = new RemoteWallpaperEffectsGenerationService(getContext(),
+                    serviceComponent, mUserId, this,
+                    mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+        }
+
+        return mRemoteService;
+    }
+
+    @Override // from RemoteWallpaperEffectsGenerationService
+    public void onServiceDied(RemoteWallpaperEffectsGenerationService service) {
+        Slog.w(TAG, "remote wallpaper effects generation service died");
+        updateRemoteServiceLocked();
+    }
+
+    @Override // from RemoteWallpaperEffectsGenerationService
+    public void onConnectedStateChanged(boolean connected) {
+        if (!connected) {
+            Slog.w(TAG, "remote wallpaper effects generation service disconnected");
+            updateRemoteServiceLocked();
+        }
+    }
+
+    private static final class CinematicEffectListenerWrapper {
+        @NonNull
+        private final String mTaskId;
+        @NonNull
+        private final ICinematicEffectListener mListener;
+
+        CinematicEffectListenerWrapper(
+                @NonNull final String taskId,
+                @NonNull final ICinematicEffectListener listener) {
+            mTaskId = taskId;
+            mListener = listener;
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index c5fc436..27d423b 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -3156,15 +3157,27 @@
     }
 
     /**
-     * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
-     * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
-     * call created using
-     * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
+     * Calls of this type are created using
+     * {@link TelecomManager#addNewUnknownCall(PhoneAccountHandle, Bundle)}.  Unknown calls
+     * are used for representing calls which become known to the {@link ConnectionService}
+     * midway through the call.
+     *
+     * For example, a call transferred from one device to answer would surface as an active
+     * call in Telecom instead of going through a typical Ringing to Active transition, or
+     * Dialing to Active transition.
+     *
+     * A {@link ConnectionService} can return {@code null} (the default behavior)
+     * if it is not able to handle a request for the requested unknown connection.
+     *
+     * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
      *
      * @hide
      */
-    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
-            ConnectionRequest request) {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public @Nullable Connection onCreateUnknownConnection(
+            @NonNull PhoneAccountHandle connectionManagerPhoneAccount,
+            @NonNull ConnectionRequest request) {
         return null;
     }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a74930b..3c277b7 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4540,9 +4540,7 @@
      * Passing this value as {@link #KEY_SUBSCRIPTION_GROUP_UUID_STRING} will remove the
      * subscription from a group instead of adding it to a group.
      *
-     * TODO: Expose in a future release.
-     *
-     * @hide
+     * <p>This value will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
      */
     public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000";
 
@@ -4555,9 +4553,7 @@
      * <p>If set to {@link #REMOVE_GROUP_UUID_STRING}, then the subscription will be removed from
      * its current group.
      *
-     * TODO: unhide this key.
-     *
-     * @hide
+     * <p>This key will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
      */
     public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING =
             "subscription_group_uuid_string";
@@ -4605,17 +4601,15 @@
             "data_switch_validation_min_gap_long";
 
     /**
-    * A boolean property indicating whether this subscription should be managed as an opportunistic
-    * subscription.
-    *
-    * If true, then this subscription will be selected based on available coverage and will not be
-    * available for a user in settings menus for selecting macro network providers. If unset,
-    * defaults to “false”.
-    *
-    * TODO: unhide this key.
-    *
-    * @hide
-    */
+     * A boolean property indicating whether this subscription should be managed as an opportunistic
+     * subscription.
+     *
+     * If true, then this subscription will be selected based on available coverage and will not be
+     * available for a user in settings menus for selecting macro network providers. If unset,
+     * defaults to “false”.
+     *
+     * <p>This key will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
+     */
     public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL =
             "is_opportunistic_subscription_bool";
 
@@ -8920,7 +8914,7 @@
         sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true);
         sDefaults.putBoolean(KEY_HIDE_ENABLE_2G, false);
         sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY,
-                new String[]{"ia", "default", "mms", "dun"});
+                new String[]{"ia", "default"});
         sDefaults.putBoolean(KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false);
         sDefaults.putBoolean(KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL, false);
         sDefaults.putBoolean(KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL, true);
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
index 48bfd6f..6290292 100644
--- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
+++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
@@ -11,10 +11,20 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import com.google.common.truth.Truth.assertThat
+import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE
+import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE
 import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED
-import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
+import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
+import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
 import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN
+import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
+import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
 import java.lang.IllegalArgumentException
+import java.io.ByteArrayOutputStream
+import java.security.KeyPairGenerator
+import java.security.KeyStore
 import java.time.Duration
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.TimeUnit
@@ -23,25 +33,26 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class SystemAttestationVerificationTest {
-
     @get:Rule
     val rule = ActivityScenarioRule(TestActivity::class.java)
 
     private lateinit var activity: Activity
     private lateinit var avm: AttestationVerificationManager
+    private lateinit var androidKeystore: KeyStore
 
     @Before
     fun setup() {
         rule.getScenario().onActivity {
             avm = it.getSystemService(AttestationVerificationManager::class.java)
             activity = it
+            androidKeystore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
         }
     }
 
     @Test
     fun verifyAttestation_returnsUnknown() {
         val future = CompletableFuture<Int>()
-        val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
+        val profile = AttestationProfile(PROFILE_PEER_DEVICE)
         avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
                 activity.mainExecutor) { result, _ ->
             future.complete(result)
@@ -51,9 +62,82 @@
     }
 
     @Test
-    fun verifyToken_returnsUnknown() {
+    fun verifyAttestation_returnsFailureWithEmptyAttestation() {
         val future = CompletableFuture<Int>()
         val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
+        avm.verifyAttestation(profile, TYPE_CHALLENGE, Bundle(), ByteArray(0),
+            activity.mainExecutor) { result, _ ->
+            future.complete(result)
+        }
+
+        assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+    }
+
+    @Test
+    fun verifyAttestation_returnsFailureWithEmptyRequirements() {
+        val future = CompletableFuture<Int>()
+        val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+        avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+            Bundle(), selfTrusted.attestation, activity.mainExecutor) { result, _ ->
+            future.complete(result)
+        }
+        assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+    }
+
+    @Test
+    fun verifyAttestation_returnsFailureWithWrongBindingType() {
+        val future = CompletableFuture<Int>()
+        val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+        avm.verifyAttestation(selfTrusted.profile, TYPE_PUBLIC_KEY,
+            selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
+            future.complete(result)
+        }
+        assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+    }
+
+    @Test
+    fun verifyAttestation_returnsFailureWithWrongRequirements() {
+        val future = CompletableFuture<Int>()
+        val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+        val wrongKeyRequirements = Bundle()
+        wrongKeyRequirements.putByteArray(
+            "wrongBindingKey", "challengeStr".encodeToByteArray())
+        avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+            wrongKeyRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
+            future.complete(result)
+        }
+        assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+    }
+
+    @Test
+    fun verifyAttestation_returnsFailureWithWrongChallenge() {
+        val future = CompletableFuture<Int>()
+        val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+        val wrongChallengeRequirements = Bundle()
+        wrongChallengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray())
+        avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+            wrongChallengeRequirements, selfTrusted.attestation, activity.mainExecutor) {
+                result, _ -> future.complete(result)
+        }
+        assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+    }
+
+    // TODO(b/216144791): Add more failure tests for PROFILE_SELF_TRUSTED.
+    @Test
+    fun verifyAttestation_returnsSuccess() {
+        val future = CompletableFuture<Int>()
+        val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+        avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+            selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
+            future.complete(result)
+        }
+        assertThat(future.getSoon()).isEqualTo(RESULT_SUCCESS)
+    }
+
+    @Test
+    fun verifyToken_returnsUnknown() {
+        val future = CompletableFuture<Int>()
+        val profile = AttestationProfile(PROFILE_PEER_DEVICE)
         avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
                 activity.mainExecutor) { _, token ->
             val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null)
@@ -66,7 +150,7 @@
     @Test
     fun verifyToken_tooBigMaxAgeThrows() {
         val future = CompletableFuture<VerificationToken>()
-        val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
+        val profile = AttestationProfile(PROFILE_PEER_DEVICE)
         avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
                 activity.mainExecutor) { _, token ->
             future.complete(token)
@@ -87,4 +171,52 @@
             super.onCreate(savedInstanceState)
         }
     }
+
+    inner class TestSelfTrustedAttestation(val alias: String, val challenge: String) {
+        val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
+        val localBindingType = TYPE_CHALLENGE
+        val requirements: Bundle
+        val attestation: ByteArray
+
+        init {
+            val challengeByteArray = challenge.encodeToByteArray()
+            generateAndStoreKey(alias, challengeByteArray)
+            attestation = generateCertificatesByteArray(alias)
+            requirements = Bundle()
+            requirements.putByteArray(PARAM_CHALLENGE, challengeByteArray)
+        }
+
+        private fun generateAndStoreKey(alias: String, challenge: ByteArray) {
+            val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
+                KeyProperties.KEY_ALGORITHM_EC,
+                ANDROID_KEYSTORE
+            )
+            val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
+                alias,
+                KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
+            ).run {
+                // a challenge results in a generated attestation
+                setAttestationChallenge(challenge)
+                setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
+                build()
+            }
+            kpg.initialize(parameterSpec)
+            kpg.generateKeyPair()
+        }
+
+        private fun generateCertificatesByteArray(alias: String): ByteArray {
+            val pkEntry = androidKeystore.getEntry(alias, null) as KeyStore.PrivateKeyEntry
+            val certs = pkEntry.certificateChain
+            val bos = ByteArrayOutputStream()
+            certs.forEach {
+                bos.write(it.encoded)
+            }
+            return bos.toByteArray()
+        }
+    }
+
+    companion object {
+        private const val TAG = "AVFTEST"
+        private const val ANDROID_KEYSTORE = "AndroidKeyStore"
+    }
 }
diff --git a/tools/lint/README.md b/tools/lint/README.md
index 2b6d65b..b534b62 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -40,6 +40,9 @@
 - If you want to build lint reports for more than 1 module and they include a common module in their
   `defaults` field, e.g. `platform_service_defaults`, you can add the `lint` property to that common
   module instead of adding it in every module.
+- If you want to run a single lint type, use the `ANDROID_LINT_CHECK`
+  environment variable with the id of the lint. For example:
+  `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html`
 
 ## Create or update a baseline