Merge "Remove requestCursorUpdatesFromImm() again"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
deleted file mode 100644
index 78a77fe..0000000
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.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 com.android.server.job.controllers;
-
-import java.util.Objects;
-
-/** Wrapper class to represent a userId-pkgName combo. */
-final class Package {
-    public final String packageName;
-    public final int userId;
-
-    Package(int userId, String packageName) {
-        this.userId = userId;
-        this.packageName = packageName;
-    }
-
-    @Override
-    public String toString() {
-        return packageToString(userId, packageName);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof Package)) {
-            return false;
-        }
-        Package other = (Package) obj;
-        return userId == other.userId && Objects.equals(packageName, other.packageName);
-    }
-
-    @Override
-    public int hashCode() {
-        return packageName.hashCode() + userId;
-    }
-
-    /**
-     * Standardize the output of userId-packageName combo.
-     */
-    static String packageToString(int userId, String packageName) {
-        return "<" + userId + ">" + packageName;
-    }
-}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index d69b9e0..c46ffd7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -21,7 +21,6 @@
 
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 import static com.android.server.job.JobSchedulerService.sSystemClock;
-import static com.android.server.job.controllers.Package.packageToString;
 
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.ElapsedRealtimeLong;
@@ -31,6 +30,7 @@
 import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
+import android.content.pm.UserPackage;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -172,7 +172,7 @@
         final String pkgName = jobStatus.getSourcePackageName();
         final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
         if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
-            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+            mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
         }
     }
 
@@ -186,7 +186,7 @@
         final int userId = UserHandle.getUserId(uid);
         mTrackedJobs.delete(userId, packageName);
         mEstimatedLaunchTimes.delete(userId, packageName);
-        mThresholdAlarmListener.removeAlarmForKey(new Package(userId, packageName));
+        mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, packageName));
     }
 
     @Override
@@ -354,7 +354,7 @@
             @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
         final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
         if (jobs == null || jobs.size() == 0) {
-            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+            mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
             return;
         }
 
@@ -365,10 +365,10 @@
             // Set alarm to be notified when this crosses the threshold.
             final long timeToCrossThresholdMs =
                     nextEstimatedLaunchTime - (now + mLaunchTimeThresholdMs);
-            mThresholdAlarmListener.addAlarm(new Package(userId, pkgName),
+            mThresholdAlarmListener.addAlarm(UserPackage.of(userId, pkgName),
                     nowElapsed + timeToCrossThresholdMs);
         } else {
-            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+            mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
         }
     }
 
@@ -427,25 +427,25 @@
     }
 
     /** Track when apps will cross the "will run soon" threshold. */
-    private class ThresholdAlarmListener extends AlarmQueue<Package> {
+    private class ThresholdAlarmListener extends AlarmQueue<UserPackage> {
         private ThresholdAlarmListener(Context context, Looper looper) {
             super(context, looper, "*job.prefetch*", "Prefetch threshold", false,
                     PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS / 10);
         }
 
         @Override
-        protected boolean isForUser(@NonNull Package key, int userId) {
+        protected boolean isForUser(@NonNull UserPackage key, int userId) {
             return key.userId == userId;
         }
 
         @Override
-        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+        protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
             final ArraySet<JobStatus> changedJobs = new ArraySet<>();
             synchronized (mLock) {
                 final long now = sSystemClock.millis();
                 final long nowElapsed = sElapsedRealtimeClock.millis();
                 for (int i = 0; i < expired.size(); ++i) {
-                    Package p = expired.valueAt(i);
+                    UserPackage p = expired.valueAt(i);
                     if (!willBeLaunchedSoonLocked(p.userId, p.packageName, now)) {
                         Slog.e(TAG, "Alarm expired for "
                                 + packageToString(p.userId, p.packageName) + " at the wrong time");
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 659e7c0..d8206ad 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
@@ -28,7 +28,6 @@
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.controllers.Package.packageToString;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -44,6 +43,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -532,7 +532,7 @@
      */
     private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>();
 
-    /** An app has reached its quota. The message should contain a {@link Package} object. */
+    /** An app has reached its quota. The message should contain a {@link UserPackage} object. */
     @VisibleForTesting
     static final int MSG_REACHED_QUOTA = 0;
     /** Drop any old timing sessions. */
@@ -542,7 +542,7 @@
     /** Process state for a UID has changed. */
     private static final int MSG_UID_PROCESS_STATE_CHANGED = 3;
     /**
-     * An app has reached its expedited job quota. The message should contain a {@link Package}
+     * An app has reached its expedited job quota. The message should contain a {@link UserPackage}
      * object.
      */
     @VisibleForTesting
@@ -680,7 +680,7 @@
             final String pkgName = jobStatus.getSourcePackageName();
             ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
             if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
-                mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+                mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
             }
         }
     }
@@ -746,7 +746,7 @@
         }
         mTimingEvents.delete(userId, packageName);
         mEJTimingSessions.delete(userId, packageName);
-        mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+        mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
         mExecutionStatsCache.delete(userId, packageName);
         mEJStats.delete(userId, packageName);
         mTopAppTrackers.delete(userId, packageName);
@@ -1725,7 +1725,7 @@
             // exempted.
             maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
         } else {
-            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+            mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
         }
         return changedJobs;
     }
@@ -1764,7 +1764,7 @@
                     && isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
                 // TODO(141645789): we probably shouldn't cancel the alarm until we've verified
                 // that all jobs for the userId-package are within quota.
-                mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+                mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
             } else {
                 mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
             }
@@ -1814,7 +1814,7 @@
         if (jobs == null || jobs.size() == 0) {
             Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
                     + packageToString(userId, packageName) + " that has no jobs");
-            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+            mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
             return;
         }
 
@@ -1838,7 +1838,7 @@
                         + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
                         + "ms in its quota.");
             }
-            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+            mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
             mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
             return;
         }
@@ -1903,7 +1903,7 @@
                             + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats);
             inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS;
         }
-        mInQuotaAlarmQueue.addAlarm(new Package(userId, packageName), inQuotaTimeElapsed);
+        mInQuotaAlarmQueue.addAlarm(UserPackage.of(userId, packageName), inQuotaTimeElapsed);
     }
 
     private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed,
@@ -2098,7 +2098,7 @@
     }
 
     private final class Timer {
-        private final Package mPkg;
+        private final UserPackage mPkg;
         private final int mUid;
         private final boolean mRegularJobTimer;
 
@@ -2110,7 +2110,7 @@
         private long mDebitAdjustment;
 
         Timer(int uid, int userId, String packageName, boolean regularJobTimer) {
-            mPkg = new Package(userId, packageName);
+            mPkg = UserPackage.of(userId, packageName);
             mUid = uid;
             mRegularJobTimer = regularJobTimer;
         }
@@ -2365,7 +2365,7 @@
     }
 
     private final class TopAppTimer {
-        private final Package mPkg;
+        private final UserPackage mPkg;
 
         // List of jobs currently running for this app that started when the app wasn't in the
         // foreground.
@@ -2373,7 +2373,7 @@
         private long mStartTimeElapsed;
 
         TopAppTimer(int userId, String packageName) {
-            mPkg = new Package(userId, packageName);
+            mPkg = UserPackage.of(userId, packageName);
         }
 
         private int calculateTimeChunks(final long nowElapsed) {
@@ -2656,7 +2656,7 @@
             synchronized (mLock) {
                 switch (msg.what) {
                     case MSG_REACHED_QUOTA: {
-                        Package pkg = (Package) msg.obj;
+                        UserPackage pkg = (UserPackage) msg.obj;
                         if (DEBUG) {
                             Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
                         }
@@ -2685,7 +2685,7 @@
                         break;
                     }
                     case MSG_REACHED_EJ_QUOTA: {
-                        Package pkg = (Package) msg.obj;
+                        UserPackage pkg = (UserPackage) msg.obj;
                         if (DEBUG) {
                             Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
                         }
@@ -2887,21 +2887,21 @@
     }
 
     /** Track when UPTCs are expected to come back into quota. */
-    private class InQuotaAlarmQueue extends AlarmQueue<Package> {
+    private class InQuotaAlarmQueue extends AlarmQueue<UserPackage> {
         private InQuotaAlarmQueue(Context context, Looper looper) {
             super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false,
                     QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
         }
 
         @Override
-        protected boolean isForUser(@NonNull Package key, int userId) {
+        protected boolean isForUser(@NonNull UserPackage key, int userId) {
             return key.userId == userId;
         }
 
         @Override
-        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+        protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
             for (int i = 0; i < expired.size(); ++i) {
-                Package p = expired.valueAt(i);
+                UserPackage p = expired.valueAt(i);
                 mHandler.obtainMessage(MSG_CHECK_PACKAGE, p.userId, 0, p.packageName)
                         .sendToTarget();
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 0eedcf0..44ac798 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -186,4 +186,11 @@
     /** Dump any internal constants the Controller may have. */
     public void dumpConstants(ProtoOutputStream proto) {
     }
+
+    /**
+     * Standardize the output of userId-packageName combo.
+     */
+    static String packageToString(int userId, String packageName) {
+        return "<" + userId + ">" + packageName;
+    }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 7a13e3f..abc196f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -36,6 +36,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.UserPackage;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -824,7 +825,7 @@
     void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) {
         mScribe.discardLedgerLocked(userId, pkgName);
         mCurrentOngoingEvents.delete(userId, pkgName);
-        mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+        mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
     }
 
     @GuardedBy("mLock")
@@ -959,7 +960,7 @@
                 mCurrentOngoingEvents.get(userId, pkgName);
         if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) {
             // No ongoing transactions. No reason to schedule
-            mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+            mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
             return;
         }
         mTrendCalculator.reset(getBalanceLocked(userId, pkgName),
@@ -972,7 +973,7 @@
         if (lowerTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) {
             if (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) {
                 // Will never cross a threshold based on current events.
-                mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+                mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
                 return;
             }
             timeToThresholdMs = upperTimeMs;
@@ -980,7 +981,7 @@
             timeToThresholdMs = (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD)
                     ? lowerTimeMs : Math.min(lowerTimeMs, upperTimeMs);
         }
-        mBalanceThresholdAlarmQueue.addAlarm(new Package(userId, pkgName),
+        mBalanceThresholdAlarmQueue.addAlarm(UserPackage.of(userId, pkgName),
                 SystemClock.elapsedRealtime() + timeToThresholdMs);
     }
 
@@ -1071,57 +1072,22 @@
 
     private final OngoingEventUpdater mOngoingEventUpdater = new OngoingEventUpdater();
 
-    private static final class Package {
-        public final String packageName;
-        public final int userId;
-
-        Package(int userId, String packageName) {
-            this.userId = userId;
-            this.packageName = packageName;
-        }
-
-        @Override
-        public String toString() {
-            return appToString(userId, packageName);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == null) {
-                return false;
-            }
-            if (this == obj) {
-                return true;
-            }
-            if (obj instanceof Package) {
-                Package other = (Package) obj;
-                return userId == other.userId && Objects.equals(packageName, other.packageName);
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return packageName.hashCode() + userId;
-        }
-    }
-
     /** Track when apps will cross the closest affordability threshold (in both directions). */
-    private class BalanceThresholdAlarmQueue extends AlarmQueue<Package> {
+    private class BalanceThresholdAlarmQueue extends AlarmQueue<UserPackage> {
         private BalanceThresholdAlarmQueue(Context context, Looper looper) {
             super(context, looper, ALARM_TAG_AFFORDABILITY_CHECK, "Affordability check", true,
                     15_000L);
         }
 
         @Override
-        protected boolean isForUser(@NonNull Package key, int userId) {
+        protected boolean isForUser(@NonNull UserPackage key, int userId) {
             return key.userId == userId;
         }
 
         @Override
-        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+        protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
             for (int i = 0; i < expired.size(); ++i) {
-                Package p = expired.valueAt(i);
+                UserPackage p = expired.valueAt(i);
                 mHandler.obtainMessage(
                         MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName)
                         .sendToTarget();
diff --git a/cmds/uiautomator/OWNERS b/cmds/uiautomator/OWNERS
new file mode 100644
index 0000000..5c7f452
--- /dev/null
+++ b/cmds/uiautomator/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 833089
+peykov@google.com
+normancheung@google.com
+guran@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index 164e14a..e2f2fae 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8956,9 +8956,18 @@
 
 package android.companion {
 
+  public final class AssociatedDevice implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.bluetooth.le.ScanResult getBleDevice();
+    method @Nullable public android.bluetooth.BluetoothDevice getBluetoothDevice();
+    method @Nullable public android.net.wifi.ScanResult getWifiDevice();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociatedDevice> CREATOR;
+  }
+
   public final class AssociationInfo implements android.os.Parcelable {
     method public int describeContents();
-    method @Nullable public android.os.Parcelable getAssociatedDevice();
+    method @Nullable public android.companion.AssociatedDevice getAssociatedDevice();
     method @Nullable public android.net.MacAddress getDeviceMacAddress();
     method @Nullable public String getDeviceProfile();
     method @Nullable public CharSequence getDisplayName();
@@ -18425,7 +18434,7 @@
     method public android.graphics.Point getLeftEyePosition();
     method public android.graphics.Point getMouthPosition();
     method public android.graphics.Point getRightEyePosition();
-    method public int getScore();
+    method @IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) public int getScore();
     field public static final int ID_UNSUPPORTED = -1; // 0xffffffff
     field public static final int SCORE_MAX = 100; // 0x64
     field public static final int SCORE_MIN = 1; // 0x1
@@ -18440,7 +18449,7 @@
     method @NonNull public android.hardware.camera2.params.Face.Builder setLeftEyePosition(@NonNull android.graphics.Point);
     method @NonNull public android.hardware.camera2.params.Face.Builder setMouthPosition(@NonNull android.graphics.Point);
     method @NonNull public android.hardware.camera2.params.Face.Builder setRightEyePosition(@NonNull android.graphics.Point);
-    method @NonNull public android.hardware.camera2.params.Face.Builder setScore(int);
+    method @NonNull public android.hardware.camera2.params.Face.Builder setScore(@IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) int);
   }
 
   public final class InputConfiguration {
@@ -19481,6 +19490,7 @@
 
   public final class GnssCapabilities implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<android.location.GnssSignalType> getGnssSignalTypes();
     method public boolean hasAntennaInfo();
     method public boolean hasGeofencing();
     method @Deprecated public boolean hasGnssAntennaInfo();
@@ -19514,6 +19524,7 @@
     ctor public GnssCapabilities.Builder();
     ctor public GnssCapabilities.Builder(@NonNull android.location.GnssCapabilities);
     method @NonNull public android.location.GnssCapabilities build();
+    method @NonNull public android.location.GnssCapabilities.Builder setGnssSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
     method @NonNull public android.location.GnssCapabilities.Builder setHasAntennaInfo(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasGeofencing(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean);
@@ -19726,6 +19737,16 @@
     field @Deprecated public static final int STATUS_READY = 1; // 0x1
   }
 
+  public final class GnssSignalType implements android.os.Parcelable {
+    method @NonNull public static android.location.GnssSignalType create(int, @FloatRange(from=0.0f, fromInclusive=false) double, @NonNull String);
+    method public int describeContents();
+    method @FloatRange(from=0.0f, fromInclusive=false) public double getCarrierFrequencyHz();
+    method @NonNull public String getCodeType();
+    method public int getConstellationType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssSignalType> CREATOR;
+  }
+
   public final class GnssStatus implements android.os.Parcelable {
     method public int describeContents();
     method @FloatRange(from=0, to=360) public float getAzimuthDegrees(@IntRange(from=0) int);
@@ -50091,7 +50112,7 @@
     method public void dispatchCreateViewTranslationRequest(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @NonNull android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);
     method public void dispatchDisplayHint(int);
     method public boolean dispatchDragEvent(android.view.DragEvent);
-    method protected void dispatchDraw(android.graphics.Canvas);
+    method protected void dispatchDraw(@NonNull android.graphics.Canvas);
     method public void dispatchDrawableHotspotChanged(float, float);
     method @CallSuper public void dispatchFinishTemporaryDetach();
     method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
@@ -50129,7 +50150,7 @@
     method @NonNull public android.view.WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimation, @NonNull android.view.WindowInsetsAnimation.Bounds);
     method @Deprecated public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
-    method @CallSuper public void draw(android.graphics.Canvas);
+    method @CallSuper public void draw(@NonNull android.graphics.Canvas);
     method @CallSuper public void drawableHotspotChanged(float, float);
     method @CallSuper protected void drawableStateChanged();
     method public android.view.View findFocus();
@@ -50422,9 +50443,9 @@
     method @CallSuper protected void onDetachedFromWindow();
     method protected void onDisplayHint(int);
     method public boolean onDragEvent(android.view.DragEvent);
-    method protected void onDraw(android.graphics.Canvas);
-    method public void onDrawForeground(android.graphics.Canvas);
-    method protected final void onDrawScrollBars(android.graphics.Canvas);
+    method protected void onDraw(@NonNull android.graphics.Canvas);
+    method public void onDrawForeground(@NonNull android.graphics.Canvas);
+    method protected final void onDrawScrollBars(@NonNull android.graphics.Canvas);
     method public boolean onFilterTouchEventForSecurity(android.view.MotionEvent);
     method @CallSuper protected void onFinishInflate();
     method public void onFinishTemporaryDetach();
@@ -50910,7 +50931,7 @@
     ctor public View.DragShadowBuilder(android.view.View);
     ctor public View.DragShadowBuilder();
     method public final android.view.View getView();
-    method public void onDrawShadow(android.graphics.Canvas);
+    method public void onDrawShadow(@NonNull android.graphics.Canvas);
     method public void onProvideShadowMetrics(android.graphics.Point, android.graphics.Point);
   }
 
@@ -51140,7 +51161,7 @@
     method public void dispatchSetActivated(boolean);
     method public void dispatchSetSelected(boolean);
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
-    method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
+    method protected boolean drawChild(@NonNull android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
@@ -53353,6 +53374,7 @@
     method public android.graphics.Matrix getMatrix();
     method public int getSelectionEnd();
     method public int getSelectionStart();
+    method @Nullable public android.view.inputmethod.TextAppearanceInfo getTextAppearanceInfo();
     method @NonNull public java.util.List<android.graphics.RectF> getVisibleLineBounds();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.CursorAnchorInfo> CREATOR;
@@ -53373,6 +53395,7 @@
     method public android.view.inputmethod.CursorAnchorInfo.Builder setInsertionMarkerLocation(float, float, float, float, int);
     method public android.view.inputmethod.CursorAnchorInfo.Builder setMatrix(android.graphics.Matrix);
     method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int);
+    method @NonNull public android.view.inputmethod.CursorAnchorInfo.Builder setTextAppearanceInfo(@Nullable android.view.inputmethod.TextAppearanceInfo);
   }
 
   public final class DeleteGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
@@ -53628,6 +53651,7 @@
     field public static final int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 8; // 0x8
     field public static final int CURSOR_UPDATE_FILTER_EDITOR_BOUNDS = 4; // 0x4
     field public static final int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 16; // 0x10
+    field public static final int CURSOR_UPDATE_FILTER_TEXT_APPEARANCE = 64; // 0x40
     field public static final int CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS = 32; // 0x20
     field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1
     field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
@@ -53931,6 +53955,35 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SurroundingText> CREATOR;
   }
 
+  public final class TextAppearanceInfo implements android.os.Parcelable {
+    ctor public TextAppearanceInfo(@NonNull android.widget.TextView);
+    method public int describeContents();
+    method @Nullable public String getFontFamilyName();
+    method @Nullable public String getFontFeatureSettings();
+    method @Nullable public String getFontVariationSettings();
+    method public float getLetterSpacing();
+    method public int getLineBreakStyle();
+    method public int getLineBreakWordStyle();
+    method public int getMaxLength();
+    method @Px public float getShadowDx();
+    method @Px public float getShadowDy();
+    method @Px public float getShadowRadius();
+    method @ColorInt public int getTextColor();
+    method @ColorInt public int getTextColorHighlight();
+    method @ColorInt public int getTextColorHint();
+    method @Nullable public android.content.res.ColorStateList getTextColorLink();
+    method @IntRange(from=0xffffffff, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
+    method @NonNull public android.os.LocaleList getTextLocales();
+    method public float getTextScaleX();
+    method @Px public float getTextSize();
+    method public int getTextStyle();
+    method public boolean isAllCaps();
+    method public boolean isElegantTextHeight();
+    method public boolean isFallbackLineSpacing();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAppearanceInfo> CREATOR;
+  }
+
   public final class TextAttribute implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.os.PersistableBundle getExtras();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 352b4f9..70b89b8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6346,12 +6346,21 @@
   public final class AudioPlaybackConfiguration implements android.os.Parcelable {
     method public int getClientPid();
     method public int getClientUid();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMutedBy();
     method public int getPlayerInterfaceId();
     method public android.media.PlayerProxy getPlayerProxy();
     method public int getPlayerState();
     method public int getPlayerType();
     method @IntRange(from=0) public int getSessionId();
     method public boolean isActive();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_UNKNOWN = -1; // 0xffffffff
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20
     field public static final int PLAYER_STATE_IDLE = 1; // 0x1
     field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
     field public static final int PLAYER_STATE_RELEASED = 0; // 0x0
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cdb1510..e9f9136 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3159,7 +3159,7 @@
   public final class InputMethodManager {
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
     method public int getDisplayId();
-    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
+    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index c2c065b..45515dd 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -117,27 +117,27 @@
     /**
      * Bundle key used for the {@link String} account type in session bundle.
      * This is used in the default implementation of
-     * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+     * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_AUTH_TOKEN_TYPE =
             "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
     /**
      * Bundle key used for the {@link String} array of required features in
      * session bundle. This is used in the default implementation of
-     * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+     * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_REQUIRED_FEATURES =
             "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
     /**
      * Bundle key used for the {@link Bundle} options in session bundle. This is
      * used in default implementation of {@link #startAddAccountSession} and
-     * {@link startUpdateCredentialsSession}.
+     * {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_OPTIONS =
             "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
     /**
      * Bundle key used for the {@link Account} account in session bundle. This is used
-     * used in default implementation of {@link startUpdateCredentialsSession}.
+     * used in default implementation of {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_ACCOUNT =
             "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
@@ -154,6 +154,8 @@
         public void addAccount(IAccountAuthenticatorResponse response, String accountType,
                 String authTokenType, String[] features, Bundle options)
                 throws RemoteException {
+            super.addAccount_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "addAccount: accountType " + accountType
                         + ", authTokenType " + authTokenType
@@ -184,6 +186,8 @@
         @Override
         public void confirmCredentials(IAccountAuthenticatorResponse response,
                 Account account, Bundle options) throws RemoteException {
+            super.confirmCredentials_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "confirmCredentials: " + account);
             }
@@ -210,6 +214,8 @@
         public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
                 String authTokenType)
                 throws RemoteException {
+            super.getAuthTokenLabel_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
             }
@@ -235,6 +241,8 @@
         public void getAuthToken(IAccountAuthenticatorResponse response,
                 Account account, String authTokenType, Bundle loginOptions)
                 throws RemoteException {
+            super.getAuthToken_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "getAuthToken: " + account
                         + ", authTokenType " + authTokenType);
@@ -262,6 +270,8 @@
         @Override
         public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
                 String authTokenType, Bundle loginOptions) throws RemoteException {
+            super.updateCredentials_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "updateCredentials: " + account
                         + ", authTokenType " + authTokenType);
@@ -291,6 +301,8 @@
         @Override
         public void editProperties(IAccountAuthenticatorResponse response,
                 String accountType) throws RemoteException {
+            super.editProperties_enforcePermission();
+
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.editProperties(
                     new AccountAuthenticatorResponse(response), accountType);
@@ -306,6 +318,8 @@
         @Override
         public void hasFeatures(IAccountAuthenticatorResponse response,
                 Account account, String[] features) throws RemoteException {
+            super.hasFeatures_enforcePermission();
+
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.hasFeatures(
                     new AccountAuthenticatorResponse(response), account, features);
@@ -321,6 +335,8 @@
         @Override
         public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
                 Account account) throws RemoteException {
+            super.getAccountRemovalAllowed_enforcePermission();
+
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed(
                     new AccountAuthenticatorResponse(response), account);
@@ -336,6 +352,8 @@
         @Override
         public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
                 Account account) throws RemoteException {
+            super.getAccountCredentialsForCloning_enforcePermission();
+
             try {
                 final Bundle result =
                         AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
@@ -353,6 +371,8 @@
         public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
                 Account account,
                 Bundle accountCredentials) throws RemoteException {
+            super.addAccountFromCredentials_enforcePermission();
+
             try {
                 final Bundle result =
                         AbstractAccountAuthenticator.this.addAccountFromCredentials(
@@ -371,6 +391,8 @@
         public void startAddAccountSession(IAccountAuthenticatorResponse response,
                 String accountType, String authTokenType, String[] features, Bundle options)
                 throws RemoteException {
+            super.startAddAccountSession_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG,
                         "startAddAccountSession: accountType " + accountType
@@ -403,6 +425,8 @@
                 Account account,
                 String authTokenType,
                 Bundle loginOptions) throws RemoteException {
+            super.startUpdateCredentialsSession_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "startUpdateCredentialsSession: "
                         + account
@@ -441,6 +465,8 @@
                 IAccountAuthenticatorResponse response,
                 String accountType,
                 Bundle sessionBundle) throws RemoteException {
+            super.finishSession_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "finishSession: accountType " + accountType);
             }
@@ -468,6 +494,8 @@
                 IAccountAuthenticatorResponse response,
                 Account account,
                 String statusToken) throws RemoteException {
+            super.isCredentialsUpdateSuggested_enforcePermission();
+
             try {
                 final Bundle result = AbstractAccountAuthenticator.this
                         .isCredentialsUpdateSuggested(
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index a573776..b3db38d 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -27,7 +27,6 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.UserHandleAware;
-import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -37,6 +36,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.database.SQLException;
 import android.os.Build;
@@ -348,43 +348,11 @@
     */
     public static final int CACHE_ACCOUNTS_DATA_SIZE = 4;
 
-    private static final class UserIdPackage
-    {
-        @UserIdInt
-        public int userId;
-        public String packageName;
-
-        public UserIdPackage(int UserId, String PackageName) {
-            this.userId = UserId;
-            this.packageName = PackageName;
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (o == null) {
-                return false;
-            }
-            if (o == this) {
-                return true;
-            }
-            if (o.getClass() != getClass()) {
-                return false;
-            }
-            UserIdPackage e = (UserIdPackage) o;
-            return e.userId == userId && e.packageName.equals(packageName);
-        }
-
-        @Override
-        public int hashCode() {
-            return userId ^ packageName.hashCode();
-        }
-    }
-
-    PropertyInvalidatedCache<UserIdPackage, Account[]> mAccountsForUserCache =
-                new PropertyInvalidatedCache<UserIdPackage, Account[]>(
+    PropertyInvalidatedCache<UserPackage, Account[]> mAccountsForUserCache =
+                new PropertyInvalidatedCache<UserPackage, Account[]>(
                 CACHE_ACCOUNTS_DATA_SIZE, CACHE_KEY_ACCOUNTS_DATA_PROPERTY) {
         @Override
-        public Account[] recompute(UserIdPackage userAndPackage) {
+        public Account[] recompute(UserPackage userAndPackage) {
             try {
                 return mService.getAccountsAsUser(null, userAndPackage.userId, userAndPackage.packageName);
             } catch (RemoteException e) {
@@ -392,7 +360,7 @@
             }
         }
         @Override
-        public boolean bypass(UserIdPackage query) {
+        public boolean bypass(UserPackage query) {
             return query.userId < 0;
         }
         @Override
@@ -731,7 +699,7 @@
      */
     @NonNull
     public Account[] getAccountsAsUser(int userId) {
-        UserIdPackage userAndPackage = new UserIdPackage(userId, mContext.getOpPackageName());
+        UserPackage userAndPackage = UserPackage.of(userId, mContext.getOpPackageName());
         return mAccountsForUserCache.query(userAndPackage);
     }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 32d0d75..501b136 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8364,11 +8364,17 @@
     }
 
     final void performNewIntent(@NonNull Intent intent) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performNewIntent");
         mCanEnterPictureInPicture = true;
         onNewIntent(intent);
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void performStart(String reason) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStart:"
+                    + mComponent.getClassName());
+        }
         dispatchActivityPreStarted();
         mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
         mFragments.noteStateNotSaved();
@@ -8415,6 +8421,7 @@
 
         mActivityTransitionState.enterReady(this);
         dispatchActivityPostStarted();
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     /**
@@ -8423,7 +8430,8 @@
      *              The option to not start immediately is needed in case a transaction with
      *              multiple lifecycle transitions is in progress.
      */
-    final void performRestart(boolean start, String reason) {
+    final void performRestart(boolean start) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performRestart");
         mCanEnterPictureInPicture = true;
         mFragments.noteStateNotSaved();
 
@@ -8458,17 +8466,18 @@
             final long startTime = SystemClock.uptimeMillis();
             mInstrumentation.callActivityOnRestart(this);
             final long duration = SystemClock.uptimeMillis() - startTime;
-            EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), reason,
-                    duration);
+            EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(),
+                    "performRestart", duration);
             if (!mCalled) {
                 throw new SuperNotCalledException(
                     "Activity " + mComponent.toShortString() +
                     " did not call through to super.onRestart()");
             }
             if (start) {
-                performStart(reason);
+                performStart("performRestart");
             }
         }
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void performResume(boolean followedByPause, String reason) {
@@ -8477,7 +8486,6 @@
                     + mComponent.getClassName());
         }
         dispatchActivityPreResumed();
-        performRestart(true /* start */, reason);
 
         mFragments.execPendingActions();
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 67d4416..1f63343 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -173,7 +173,6 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 import android.view.Display;
-import android.view.DisplayAdjustments;
 import android.view.SurfaceControl;
 import android.view.ThreadedRenderer;
 import android.view.View;
@@ -244,7 +243,6 @@
 import java.util.TimeZone;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
 
 /**
  * This manages the execution of the main thread in an
@@ -409,7 +407,6 @@
     boolean mInstrumentingWithoutRestart;
     boolean mSystemThread = false;
     boolean mSomeActivitiesChanged = false;
-    /* package */ boolean mHiddenApiWarningShown = false;
 
     // These can be accessed by multiple threads; mResourcesManager is the lock.
     // XXX For now we keep around information about all packages we have
@@ -438,16 +435,10 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final ResourcesManager mResourcesManager;
 
-    /** The active adjustments that override the {@link DisplayAdjustments} in resources. */
-    private ArrayList<Pair<IBinder, Consumer<DisplayAdjustments>>> mActiveRotationAdjustments;
-
     // Registry of remote cancellation transports pending a reply with reply handles.
     @GuardedBy("this")
     private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;
 
-    private final Map<IBinder, Integer> mLastReportedWindowingMode = Collections.synchronizedMap(
-            new ArrayMap<>());
-
     private static final class ProviderKey {
         final String authority;
         final int userId;
@@ -518,8 +509,6 @@
      */
     private final Object mCoreSettingsLock = new Object();
 
-    boolean mHasImeComponent = false;
-
     private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null;
 
     /** A client side controller to handle process level configuration changes. */
@@ -599,6 +588,12 @@
         /** Whether this activiy was launched from a bubble. */
         boolean mLaunchedFromBubble;
 
+        /**
+         * This can be different from the current configuration because a new configuration may not
+         * always update to activity, e.g. windowing mode change without size change.
+         */
+        int mLastReportedWindowingMode = WINDOWING_MODE_UNDEFINED;
+
         @LifecycleState
         private int mLifecycleState = PRE_ON_CREATE;
 
@@ -3654,8 +3649,7 @@
                         "Activity " + r.intent.getComponent().toShortString() +
                         " did not call through to super.onCreate()");
                 }
-                mLastReportedWindowingMode.put(activity.getActivityToken(),
-                        config.windowConfiguration.getWindowingMode());
+                r.mLastReportedWindowingMode = config.windowConfiguration.getWindowingMode();
             }
             r.setState(ON_CREATE);
 
@@ -3714,12 +3708,14 @@
         // Call postOnCreate()
         if (pendingActions.shouldCallOnPostCreate()) {
             activity.mCalled = false;
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onPostCreate");
             if (r.isPersistable()) {
                 mInstrumentation.callActivityOnPostCreate(activity, r.state,
                         r.persistentState);
             } else {
                 mInstrumentation.callActivityOnPostCreate(activity, r.state);
             }
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             if (!activity.mCalled) {
                 throw new SuperNotCalledException(
                         "Activity " + r.intent.getComponent().toShortString()
@@ -5268,7 +5264,7 @@
     @Override
     public void performRestartActivity(ActivityClientRecord r, boolean start) {
         if (r.stopped) {
-            r.activity.performRestart(start, "performRestartActivity");
+            r.activity.performRestart(start);
             if (start) {
                 r.setState(ON_START);
             }
@@ -5285,7 +5281,7 @@
     private void onCoreSettingsChange() {
         if (updateDebugViewAttributeState()) {
             // request all activities to relaunch for the changes to take place
-            relaunchAllActivities(false /* preserveWindows */, "onCoreSettingsChange");
+            relaunchAllActivities(true /* preserveWindows */, "onCoreSettingsChange");
         }
     }
 
@@ -5433,7 +5429,6 @@
             }
         }
         r.setState(ON_DESTROY);
-        mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         schedulePurgeIdler();
         synchronized (this) {
             if (mSplashScreenGlobal != null) {
@@ -5865,7 +5860,7 @@
         if (r.overrideConfig != null) {
             r.tmpConfig.updateFrom(r.overrideConfig);
         }
-        final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
+        final Configuration reportedConfig = performActivityConfigurationChanged(r,
                 r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);
         freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
         return reportedConfig;
@@ -5873,7 +5868,7 @@
 
     /**
      * Decides whether to update an Activity's configuration and whether to inform it.
-     * @param activity The activity to notify of configuration change.
+     * @param r The activity client record to notify of configuration change.
      * @param newConfig The new configuration.
      * @param amOverrideConfig The override config that differentiates the Activity's configuration
      *                         from the base global configuration. This is supplied by
@@ -5881,29 +5876,29 @@
      * @param displayId Id of the display where activity currently resides.
      * @return Configuration sent to client, null if no changes and not moved to different display.
      */
-    private Configuration performActivityConfigurationChanged(Activity activity,
+    private Configuration performActivityConfigurationChanged(ActivityClientRecord r,
             Configuration newConfig, Configuration amOverrideConfig, int displayId,
             boolean alwaysReportChange) {
+        final Activity activity = r.activity;
         final IBinder activityToken = activity.getActivityToken();
 
         // WindowConfiguration differences aren't considered as public, check it separately.
         // multi-window / pip mode changes, if any, should be sent before the configuration
         // change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
-        handleWindowingModeChangeIfNeeded(activity, newConfig);
+        handleWindowingModeChangeIfNeeded(r, newConfig);
 
         final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
                 displayId);
         final Configuration currentResConfig = activity.getResources().getConfiguration();
         final int diff = currentResConfig.diffPublicOnly(newConfig);
         final boolean hasPublicResConfigChange = diff != 0;
-        final ActivityClientRecord r = getActivityClient(activityToken);
         // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
         // ResourcesImpl constructions.
         final boolean shouldUpdateResources = hasPublicResConfigChange
                 || shouldUpdateResources(activityToken, currentResConfig, newConfig,
                 amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
         final boolean shouldReportChange = shouldReportChange(
-                activity.mCurrentConfig, newConfig, r != null ? r.mSizeConfigurations : null,
+                activity.mCurrentConfig, newConfig, r.mSizeConfigurations,
                 activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
         // Nothing significant, don't proceed with updating and reporting.
         if (!shouldUpdateResources && !shouldReportChange) {
@@ -6012,12 +6007,11 @@
      * See also {@link Activity#onMultiWindowModeChanged(boolean, Configuration)} and
      * {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)}
      */
-    private void handleWindowingModeChangeIfNeeded(Activity activity,
+    private void handleWindowingModeChangeIfNeeded(ActivityClientRecord r,
             Configuration newConfiguration) {
+        final Activity activity = r.activity;
         final int newWindowingMode = newConfiguration.windowConfiguration.getWindowingMode();
-        final IBinder token = activity.getActivityToken();
-        final int oldWindowingMode = mLastReportedWindowingMode.getOrDefault(token,
-                WINDOWING_MODE_UNDEFINED);
+        final int oldWindowingMode = r.mLastReportedWindowingMode;
         if (oldWindowingMode == newWindowingMode) return;
         // PiP callback is sent before the MW one.
         if (newWindowingMode == WINDOWING_MODE_PINNED) {
@@ -6032,7 +6026,7 @@
         if (wasInMultiWindowMode != nowInMultiWindowMode) {
             activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration);
         }
-        mLastReportedWindowingMode.put(token, newWindowingMode);
+        r.mLastReportedWindowingMode = newWindowingMode;
     }
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 267e5b6..dc325ff 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1368,9 +1368,31 @@
     public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
             AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
 
+    /**
+     * Prevent an app from being placed into app standby buckets.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_APP_STANDBY;
+
+    /**
+     * Prevent an app from being placed into forced app standby.
+     * {@link ActivityManager#isBackgroundRestricted()}
+     * {@link #OP_RUN_ANY_IN_BACKGROUND}
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 124;
+    public static final int _NUM_OP = 126;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1869,6 +1891,28 @@
      */
     public static final String OPSTR_RUN_LONG_JOBS = "android:run_long_jobs";
 
+    /**
+     * Prevent an app from being placed into app standby buckets.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+            "android:system_exempt_from_app_standby";
+
+    /**
+     * Prevent an app from being placed into forced app standby.
+     * {@link ActivityManager#isBackgroundRestricted()}
+     * {@link #OP_RUN_ANY_IN_BACKGROUND}
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+            "android:system_exempt_from_forced_app_standby";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2350,7 +2394,13 @@
             new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED,
                     OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED")
                     .setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
-                    .setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
+                    .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+                OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+                "SYSTEM_EXEMPT_FROM_APP_STANDBY").build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+                OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+                "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
     };
 
     /**
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 0f26818..ef10c0b 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -115,6 +115,15 @@
             "file_patterns": ["(/|^)VoiceInteract[^/]*"]
         },
         {
+            "name": "CtsLocalVoiceInteraction",
+            "options": [
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                }
+            ],
+            "file_patterns": ["(/|^)VoiceInteract[^/]*"]
+        },
+        {
             "name": "CtsOsTestCases",
             "options": [
                 {
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index a2dc47d..5a2f261 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -458,7 +458,8 @@
                 && isVisible == that.isVisible
                 && isSleeping == that.isSleeping
                 && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
-                && parentTaskId == that.parentTaskId;
+                && parentTaskId == that.parentTaskId
+                && Objects.equals(topActivity, that.topActivity);
     }
 
     /**
diff --git a/core/java/android/companion/AssociatedDevice.java b/core/java/android/companion/AssociatedDevice.java
index 3758cdb..a833661 100644
--- a/core/java/android/companion/AssociatedDevice.java
+++ b/core/java/android/companion/AssociatedDevice.java
@@ -16,6 +16,7 @@
 
 package android.companion;
 
+import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,19 +24,14 @@
 import androidx.annotation.Nullable;
 
 /**
- * Loose wrapper around device parcelable. Device can be one of three types:
+ * Container for device info from an association that is not self-managed.
+ * Device can be one of three types:
  *
  * <ul>
  *     <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
  *     <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
  *     <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
  * </ul>
- *
- * This class serves as temporary wrapper to deliver a loosely-typed parcelable object from
- * {@link com.android.companiondevicemanager.CompanionDeviceActivity} to the Companion app,
- * and should only be used internally.
- *
- * @hide
  */
 public final class AssociatedDevice implements Parcelable {
     private static final int CLASSIC_BLUETOOTH = 0;
@@ -44,6 +40,7 @@
 
     @NonNull private final Parcelable mDevice;
 
+    /** @hide */
     public AssociatedDevice(@NonNull Parcelable device) {
         mDevice = device;
     }
@@ -54,11 +51,39 @@
     }
 
     /**
-     * Return device info. Cast to expected device type.
+     * Return bluetooth device info. Null if associated device is not a bluetooth device.
+     * @return Remote bluetooth device details containing MAC address.
      */
-    @NonNull
-    public Parcelable getDevice() {
-        return mDevice;
+    @Nullable
+    public BluetoothDevice getBluetoothDevice() {
+        if (mDevice instanceof BluetoothDevice) {
+            return (BluetoothDevice) mDevice;
+        }
+        return null;
+    }
+
+    /**
+     * Return bluetooth LE device info. Null if associated device is not a BLE device.
+     * @return BLE scan result containing details of detected BLE device.
+     */
+    @Nullable
+    public android.bluetooth.le.ScanResult getBleDevice() {
+        if (mDevice instanceof android.bluetooth.le.ScanResult) {
+            return (android.bluetooth.le.ScanResult) mDevice;
+        }
+        return null;
+    }
+
+    /**
+     * Return Wi-Fi device info. Null if associated device is not a Wi-Fi device.
+     * @return Wi-Fi scan result containing details of detected access point.
+     */
+    @Nullable
+    public android.net.wifi.ScanResult getWifiDevice() {
+        if (mDevice instanceof android.net.wifi.ScanResult) {
+            return (android.net.wifi.ScanResult) mDevice;
+        }
+        return null;
     }
 
     @Override
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 93964b3..5fd39fe 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -164,20 +164,19 @@
 
     /**
      * Companion device that was associated. Note that this field is not persisted across sessions.
-     *
-     * Cast to expected device type before use:
+     * Device can be one of the following types:
      *
      * <ul>
-     *     <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
-     *     <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
-     *     <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
+     *     <li>for classic Bluetooth - {@link AssociatedDevice#getBluetoothDevice()}</li>
+     *     <li>for Bluetooth LE - {@link AssociatedDevice#getBleDevice()}</li>
+     *     <li>for WiFi - {@link AssociatedDevice#getWifiDevice()}</li>
      * </ul>
      *
      * @return the companion device that was associated, or {@code null} if the device is
-     *         self-managed.
+     *         self-managed or this association info was retrieved from persistent storage.
      */
-    public @Nullable Parcelable getAssociatedDevice() {
-        return mAssociatedDevice == null ? null : mAssociatedDevice.getDevice();
+    public @Nullable AssociatedDevice getAssociatedDevice() {
+        return mAssociatedDevice;
     }
 
     /**
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b0c6cbc..e981581 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -29,6 +29,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.UserHandle;
 import android.permission.PermissionManager;
 import android.util.ArraySet;
 
@@ -297,7 +298,7 @@
     public boolean checkCallingUid() {
         final int callingUid = Binder.getCallingUid();
         if (callingUid != Process.ROOT_UID
-                && callingUid != Process.SYSTEM_UID
+                && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID
                 && callingUid != mAttributionSourceState.uid) {
             return false;
         }
diff --git a/core/java/android/content/pm/UserPackage.java b/core/java/android/content/pm/UserPackage.java
new file mode 100644
index 0000000..e75f551
--- /dev/null
+++ b/core/java/android/content/pm/UserPackage.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Objects;
+
+/**
+ * POJO to represent a package for a specific user ID.
+ *
+ * @hide
+ */
+public final class UserPackage {
+    @UserIdInt
+    public final int userId;
+    public final String packageName;
+
+    @GuardedBy("sCache")
+    private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>();
+
+    private static final Object sUserIdLock = new Object();
+    private static final class NoPreloadHolder {
+        /** Set of userIDs to cache objects for. */
+        @GuardedBy("sUserIdLock")
+        private static int[] sUserIds = new int[]{UserHandle.getUserId(Process.myUid())};
+    }
+
+    private UserPackage(int userId, String packageName) {
+        this.userId = userId;
+        this.packageName = packageName;
+    }
+
+    @Override
+    public String toString() {
+        return "<" + userId + ">" + packageName;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof UserPackage) {
+            UserPackage other = (UserPackage) obj;
+            return userId == other.userId && Objects.equals(packageName, other.packageName);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        result = 31 * result + userId;
+        result = 31 * result + packageName.hashCode();
+        return result;
+    }
+
+    /** Return an instance of this class representing the given userId + packageName combination. */
+    @NonNull
+    public static UserPackage of(@UserIdInt int userId, @NonNull String packageName) {
+        synchronized (sUserIdLock) {
+            if (!ArrayUtils.contains(NoPreloadHolder.sUserIds, userId)) {
+                // Don't cache objects for invalid userIds.
+                return new UserPackage(userId, packageName);
+            }
+        }
+        synchronized (sCache) {
+            UserPackage up = sCache.get(userId, packageName);
+            if (up == null) {
+                packageName = packageName.intern();
+                up = new UserPackage(userId, packageName);
+                sCache.add(userId, packageName, up);
+            }
+            return up;
+        }
+    }
+
+    /** Remove the specified app from the cache. */
+    public static void removeFromCache(@UserIdInt int userId, @NonNull String packageName) {
+        synchronized (sCache) {
+            sCache.delete(userId, packageName);
+        }
+    }
+
+    /** Indicate the list of valid user IDs on the device. */
+    public static void setValidUserIds(@NonNull int[] userIds) {
+        userIds = userIds.clone();
+        synchronized (sUserIdLock) {
+            NoPreloadHolder.sUserIds = userIds;
+        }
+        synchronized (sCache) {
+            for (int u = sCache.numMaps() - 1; u >= 0; --u) {
+                final int userId = sCache.keyAt(u);
+                // Not holding sUserIdLock is intentional here. We don't modify the elements within
+                // the array and so even if this method is called multiple times with different sets
+                // of user IDs, we want to adjust the cache based on each new array.
+                if (!ArrayUtils.contains(userIds, userId)) {
+                    sCache.deleteAt(u);
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 645a1ac..80f3264 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -43,17 +43,20 @@
     // Attribute strings for reading/writing properties to/from XML.
     private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
     private static final String ATTR_START_WITH_PARENT = "startWithParent";
+    private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
 
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
             INDEX_SHOW_IN_LAUNCHER,
             INDEX_START_WITH_PARENT,
+            INDEX_SHOW_IN_SETTINGS,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
     }
     private static final int INDEX_SHOW_IN_LAUNCHER = 0;
     private static final int INDEX_START_WITH_PARENT = 1;
+    private static final int INDEX_SHOW_IN_SETTINGS = 2;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -87,6 +90,37 @@
     public static final int SHOW_IN_LAUNCHER_NO = 2;
 
     /**
+     * Possible values for whether or how to show this user in the Settings app.
+     * @hide
+     */
+    @IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+            SHOW_IN_SETTINGS_WITH_PARENT,
+            SHOW_IN_SETTINGS_SEPARATE,
+            SHOW_IN_SETTINGS_NO,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShowInSettings {
+    }
+    /**
+     * Suggests that the Settings app should show this user's apps in the main tab.
+     * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+     * this user is a profile, then its apps should be shown alongside its parent's apps.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_WITH_PARENT = 0;
+    /**
+     * Suggests that the Settings app should show this user's apps, but separately from the apps of
+     * this user's parent.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_SEPARATE = 1;
+    /**
+     * Suggests that the Settings app should not show this user.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_NO = 2;
+
+    /**
      * Reference to the default user properties for this user's user type.
      * <li>If non-null, then any absent property will use the default property from here instead.
      * <li>If null, then any absent property indicates that the caller lacks permission to see it,
@@ -130,6 +164,7 @@
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
+            setShowInSettings(orig.getShowInSettings());
         }
         if (hasQueryOrManagePermission) {
             // Add items that require QUERY_USERS or stronger.
@@ -183,6 +218,33 @@
     private @ShowInLauncher int mShowInLauncher;
 
     /**
+     * Returns whether, and how, a user should be shown in the Settings app.
+     * This is generally inapplicable for non-profile users.
+     *
+     * Possible return values include
+     *    {@link #SHOW_IN_SETTINGS_WITH_PARENT}},
+     *    {@link #SHOW_IN_SETTINGS_SEPARATE},
+     *    and {@link #SHOW_IN_SETTINGS_NO}.
+     *
+     * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this
+     * property.
+     *
+     * @return whether, and how, a profile should be shown in the Settings.
+     * @hide
+     */
+    public @ShowInSettings int getShowInSettings() {
+        if (isPresent(INDEX_SHOW_IN_SETTINGS)) return mShowInSettings;
+        if (mDefaultProperties != null) return mDefaultProperties.mShowInSettings;
+        throw new SecurityException("You don't have permission to query mShowInSettings");
+    }
+    /** @hide */
+    public void setShowInSettings(@ShowInSettings int val) {
+        this.mShowInSettings = val;
+        setPresent(INDEX_SHOW_IN_SETTINGS);
+    }
+    private @ShowInSettings int mShowInSettings;
+
+    /**
      * Returns whether a profile should be started when its parent starts (unless in quiet mode).
      * This only applies for users that have parents (i.e. for profiles).
      * @hide
@@ -206,6 +268,7 @@
                 + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
                 + ", mShowInLauncher=" + getShowInLauncher()
                 + ", mStartWithParent=" + getStartWithParent()
+                + ", mShowInSettings=" + getShowInSettings()
                 + "}";
     }
 
@@ -219,6 +282,7 @@
         pw.println(prefix + "    mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
         pw.println(prefix + "    mShowInLauncher=" + getShowInLauncher());
         pw.println(prefix + "    mStartWithParent=" + getStartWithParent());
+        pw.println(prefix + "    mShowInSettings=" + getShowInSettings());
     }
 
     /**
@@ -258,6 +322,8 @@
                 case ATTR_START_WITH_PARENT:
                     setStartWithParent(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_SHOW_IN_SETTINGS:
+                    setShowInSettings(parser.getAttributeInt(i));
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -281,6 +347,9 @@
         if (isPresent(INDEX_START_WITH_PARENT)) {
             serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
         }
+        if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
+            serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -289,6 +358,7 @@
         dest.writeLong(mPropertiesPresent);
         dest.writeInt(mShowInLauncher);
         dest.writeBoolean(mStartWithParent);
+        dest.writeInt(mShowInSettings);
     }
 
     /**
@@ -301,6 +371,7 @@
         mPropertiesPresent = source.readLong();
         mShowInLauncher = source.readInt();
         mStartWithParent = source.readBoolean();
+        mShowInSettings = source.readInt();
     }
 
     @Override
@@ -327,6 +398,7 @@
         // UserProperties fields and their default values.
         private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
         private boolean mStartWithParent = false;
+        private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
 
         public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
             mShowInLauncher = showInLauncher;
@@ -338,21 +410,30 @@
             return this;
         }
 
+        /** Sets the value for {@link #mShowInSettings} */
+        public Builder setShowInSettings(@ShowInSettings int showInSettings) {
+            mShowInSettings = showInSettings;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated. */
         public UserProperties build() {
             return new UserProperties(
                     mShowInLauncher,
-                    mStartWithParent);
+                    mStartWithParent,
+                    mShowInSettings);
         }
     } // end Builder
 
     /** Creates a UserProperties with the given properties. Intended for building default values. */
     private UserProperties(
             @ShowInLauncher int showInLauncher,
-            boolean startWithParent) {
+            boolean startWithParent,
+            @ShowInSettings int showInSettings) {
 
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
+        setShowInSettings(showInSettings);
     }
 }
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 30ee118..c9a0626 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -62,10 +62,10 @@
      * <p>The execution can potentially launch UI flows to collect user consent to using a
      * credential, display a picker when multiple credentials exist, etc.
      *
-     * @param request the request specifying type(s) of credentials to get from the user.
-     * @param cancellationSignal an optional signal that allows for cancelling this call.
-     * @param executor the callback will take place on this {@link Executor}.
-     * @param callback the callback invoked when the request succeeds or fails.
+     * @param request the request specifying type(s) of credentials to get from the user
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
      */
     public void executeGetCredential(
             @NonNull GetCredentialRequest request,
@@ -101,10 +101,10 @@
      * <p>The execution can potentially launch UI flows to collect user consent to creating
      * or storing the new credential, etc.
      *
-     * @param request the request specifying type(s) of credentials to get from the user.
-     * @param cancellationSignal an optional signal that allows for cancelling this call.
-     * @param executor the callback will take place on this {@link Executor}.
-     * @param callback the callback invoked when the request succeeds or fails.
+     * @param request the request specifying type(s) of credentials to get from the user
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
      */
     public void executeCreateCredential(
             @NonNull CreateCredentialRequest request,
@@ -135,6 +135,44 @@
         }
     }
 
+    /**
+     * Clears the current user credential session from all credential providers.
+     *
+     * <p>Usually invoked after your user signs out of your app so that they will not be
+     * automatically signed in the next time.
+     *
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     *
+     * @hide
+     */
+    public void clearCredentialSession(
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "executeCreateCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote = mService.clearCredentialSession(
+                    new ClearCredentialSessionTransport(executor, callback),
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
@@ -184,4 +222,29 @@
                     () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
         }
     }
+
+    private static class ClearCredentialSessionTransport
+            extends IClearCredentialSessionCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
+
+        private ClearCredentialSessionTransport(Executor executor,
+                OutcomeReceiver<Void, CredentialManagerException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onSuccess() {
+            mCallback.onResult(null);
+        }
+
+        @Override
+        public void onError(int errorCode, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+        }
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
similarity index 66%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to core/java/android/credentials/IClearCredentialSessionCallback.aidl
index 1550ab3..903e7f5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package android.credentials;
 
-parcelable RemoteTransitionCompat;
+/**
+ * Listener for clearCredentialSession request.
+ *
+ * @hide
+ */
+interface IClearCredentialSessionCallback {
+    oneway void onSuccess();
+    oneway void onError(int errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index b0f27f9..35688d7 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -18,6 +18,7 @@
 
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
 import android.os.ICancellationSignal;
@@ -32,4 +33,6 @@
     @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
 
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
+
+    @nullable ICancellationSignal clearCredentialSession(in IClearCredentialSessionCallback callback, String callingPackage);
 }
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 99b58c9..535b551 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -73,6 +73,13 @@
             + ".extra.all_sensors";
 
     /**
+     * An extra containing the sensor type
+     * @hide
+     */
+    public static final String EXTRA_TOGGLE_TYPE = SensorPrivacyManager.class.getName()
+            + ".extra.toggle_type";
+
+    /**
      * Sensor constants which are used in {@link SensorPrivacyManager}
      */
     public static class Sensors {
diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index b9a327b..d9ee561 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -204,7 +204,7 @@
          *
          */
         @SuppressLint("MissingGetterMatchingBuilder")
-        public @NonNull Builder addOrientationForState(long deviceState, long angle) {
+        public @NonNull Builder addOrientationForState(@DeviceState long deviceState, long angle) {
             if (angle % 90 != 0) {
                 throw new IllegalArgumentException("Sensor orientation not divisible by 90: "
                         + angle);
diff --git a/core/java/android/hardware/camera2/params/Face.java b/core/java/android/hardware/camera2/params/Face.java
index 1d9a5a3a..32688a72 100644
--- a/core/java/android/hardware/camera2/params/Face.java
+++ b/core/java/android/hardware/camera2/params/Face.java
@@ -17,6 +17,7 @@
 
 package android.hardware.camera2.params;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Point;
@@ -173,6 +174,7 @@
      * @see #SCORE_MAX
      * @see #SCORE_MIN
      */
+    @IntRange(from = SCORE_MIN, to = SCORE_MAX)
     public int getScore() {
         return mScore;
     }
@@ -377,7 +379,7 @@
          * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
          * @return This builder.
          */
-        public @NonNull Builder setScore(int score) {
+        public @NonNull Builder setScore(@IntRange(from = SCORE_MIN, to = SCORE_MAX) int score) {
             checkNotUsed();
             checkScore(score);
             mBuilderFieldsSet |= FIELD_SCORE;
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
deleted file mode 100644
index 9c2aa66..0000000
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ /dev/null
@@ -1,43 +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.hardware.fingerprint;
-
-/**
- * A listener for the high-brightness mode (HBM) transitions. This allows other components to
- * perform certain actions when the HBM is toggled on or off. For example, a display manager
- * implementation can subscribe to these events from UdfpsController and adjust the display's
- * refresh rate when the HBM is enabled.
- *
- * @hide
- */
-oneway interface IUdfpsHbmListener {
-    /**
-     * UdfpsController will call this method when the HBM is enabled.
-     *
-     * @param displayId The displayId for which the HBM is enabled. See
-     *        {@link android.view.Display#getDisplayId()}.
-     */
-    void onHbmEnabled(int displayId);
-
-    /**
-     * UdfpsController will call this method when the HBM is disabled.
-     *
-     * @param displayId The displayId for which the HBM is disabled. See
-     *        {@link android.view.Display#getDisplayId()}.
-     */
-    void onHbmDisabled(int displayId);
-}
-
diff --git a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
new file mode 100644
index 0000000..8587348
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.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 android.hardware.fingerprint;
+
+/**
+ * A callback for UDFPS refresh rate. This allows other components to
+ * perform certain actions when the refresh rate is enabled or disabled.
+ * For example, a display manager implementation can subscribe to these
+ * events from UdfpsController when refresh rate is enabled or disabled.
+ *
+ * @hide
+ */
+oneway interface IUdfpsRefreshRateRequestCallback {
+    /**
+     * Sets the appropriate display refresh rate for UDFPS.
+     *
+     * @param displayId The displayId for which the refresh rate should be set. See
+     *        {@link android.view.Display#getDisplayId()}.
+     */
+    void onRequestEnabled(int displayId);
+
+    /**
+     * Unsets the appropriate display refresh rate for UDFPS.
+     *
+     * @param displayId The displayId for which the refresh rate should be unset. See
+     *        {@link android.view.Display#getDisplayId()}.
+     */
+    void onRequestDisabled(int displayId);
+}
+
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
index 20d6338..2754096 100644
--- a/core/java/android/hardware/location/ActivityRecognitionHardware.java
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -91,12 +91,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public String[] getSupportedActivities() {
+        super.getSupportedActivities_enforcePermission();
+
         return mSupportedActivities;
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public boolean isActivitySupported(String activity) {
+        super.isActivitySupported_enforcePermission();
+
         int activityType = getActivityType(activity);
         return activityType != INVALID_ACTIVITY_TYPE;
     }
@@ -104,12 +108,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+        super.registerSink_enforcePermission();
+
         return mSinks.register(sink);
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+        super.unregisterSink_enforcePermission();
+
         return mSinks.unregister(sink);
     }
 
@@ -117,6 +125,8 @@
     @Override
     public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
 
+        super.enableActivityEvent_enforcePermission();
+
         int activityType = getActivityType(activity);
         if (activityType == INVALID_ACTIVITY_TYPE) {
             return false;
@@ -134,6 +144,8 @@
     @Override
     public boolean disableActivityEvent(String activity, int eventType) {
 
+        super.disableActivityEvent_enforcePermission();
+
         int activityType = getActivityType(activity);
         if (activityType == INVALID_ACTIVITY_TYPE) {
             return false;
@@ -150,6 +162,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public boolean flush() {
+        super.flush_enforcePermission();
+
         int result = nativeFlush();
         return result == NATIVE_SUCCESS_RESULT;
     }
diff --git a/core/java/android/hardware/location/GeofenceHardwareService.java b/core/java/android/hardware/location/GeofenceHardwareService.java
index 106bfd5..99c1e16 100644
--- a/core/java/android/hardware/location/GeofenceHardwareService.java
+++ b/core/java/android/hardware/location/GeofenceHardwareService.java
@@ -79,6 +79,8 @@
         @Override
         public int[] getMonitoringTypes() {
 
+            super.getMonitoringTypes_enforcePermission();
+
             return mGeofenceHardwareImpl.getMonitoringTypes();
         }
 
@@ -86,6 +88,8 @@
         @Override
         public int getStatusOfMonitoringType(int monitoringType) {
 
+            super.getStatusOfMonitoringType_enforcePermission();
+
             return mGeofenceHardwareImpl.getStatusOfMonitoringType(monitoringType);
         }
 
@@ -95,6 +99,8 @@
                 int monitoringType,
                 GeofenceHardwareRequestParcelable request,
                 IGeofenceHardwareCallback callback) {
+            super.addCircularFence_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.addCircularFence(monitoringType, request, callback);
         }
@@ -103,6 +109,8 @@
         @Override
         public boolean removeGeofence(int id, int monitoringType) {
 
+            super.removeGeofence_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.removeGeofence(id, monitoringType);
         }
@@ -111,6 +119,8 @@
         @Override
         public boolean pauseGeofence(int id, int monitoringType) {
 
+            super.pauseGeofence_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.pauseGeofence(id, monitoringType);
         }
@@ -119,6 +129,8 @@
         @Override
         public boolean resumeGeofence(int id, int monitoringType, int monitorTransitions) {
 
+            super.resumeGeofence_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.resumeGeofence(id, monitoringType, monitorTransitions);
         }
@@ -128,6 +140,8 @@
         public boolean registerForMonitorStateChangeCallback(int monitoringType,
                 IGeofenceHardwareMonitorCallback callback) {
 
+            super.registerForMonitorStateChangeCallback_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.registerForMonitorStateChangeCallback(monitoringType,
                     callback);
@@ -138,6 +152,8 @@
         public boolean unregisterForMonitorStateChangeCallback(int monitoringType,
                 IGeofenceHardwareMonitorCallback callback) {
 
+            super.unregisterForMonitorStateChangeCallback_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.unregisterForMonitorStateChangeCallback(monitoringType,
                     callback);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 39d362b..cea46f3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1774,16 +1774,13 @@
     }
 
     /**
-     * Implement to return our standard {@link InputMethodImpl}.  Subclasses
-     * can override to provide their own customized version.
+     * Implement to return our standard {@link InputMethodImpl}.
      *
-     * @deprecated IME developers don't need to override this method to get callbacks information.
-     * Most methods in {@link InputMethodImpl} have corresponding callbacks.
-     * Use {@link InputMethodService#onBindInput()}, {@link InputMethodService#onUnbindInput()},
-     * {@link InputMethodService#onWindowShown()}, {@link InputMethodService#onWindowHidden()}, etc.
-     *
-     * <p>Starting from Android U and later, override this method won't guarantee that IME works
-     * as previous platform behavior.</p>
+     * @deprecated Overriding or calling this method is strongly discouraged. A future version of
+     * Android will remove the ability to use this method. Use the callbacks on
+     * {@link InputMethodService} as {@link InputMethodService#onBindInput()},
+     * {@link InputMethodService#onUnbindInput()}, {@link InputMethodService#onWindowShown()},
+     * {@link InputMethodService#onWindowHidden()}, etc.
      */
     @Deprecated
     @Override
@@ -1792,18 +1789,17 @@
     }
     
     /**
-     * Implement to return our standard {@link InputMethodSessionImpl}.  Subclasses
-     * can override to provide their own customized version.
+     * Implement to return our standard {@link InputMethodSessionImpl}.
      *
-     * @deprecated IME developers don't need to override this method to get callbacks information.
+     * <p>IMEs targeting on Android U and above cannot override this method, or an
+     * {@link LinkageError} would be thrown.</p>
+     *
+     * @deprecated Overriding or calling this method is strongly discouraged.
      * Most methods in {@link InputMethodSessionImpl} have corresponding callbacks.
      * Use {@link InputMethodService#onFinishInput()},
      * {@link InputMethodService#onDisplayCompletions(CompletionInfo[])},
      * {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)},
      * {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead.
-     *
-     * <p>IMEs targeting on Android U and above cannot override this method, or an
-     * {@link LinkageError} would be thrown.</p>
      */
     @Deprecated
     @Override
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 933769a..8eaa5ad 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -69,6 +69,7 @@
     boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
     UserInfo getProfileParent(int userId);
     boolean isSameProfileGroup(int userId, int otherUserHandle);
+    boolean isHeadlessSystemUserMode();
     boolean isUserOfType(int userId, in String userType);
     @UnsupportedAppUsage
     UserInfo getUserInfo(int userId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d84037f..9d8df7e 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -70,3 +70,7 @@
 
 # Tracing
 per-file Trace.java = file:/TRACE_OWNERS
+
+# PermissionEnforcer
+per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
+per-file PermissionEnforcer.java = file:/core/java/android/permission/OWNERS
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f1879dd..3d20d63 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -91,7 +91,6 @@
 public class UserManager {
 
     private static final String TAG = "UserManager";
-    private static final boolean VERBOSE = false;
 
     @UnsupportedAppUsage
     private final IUserManager mService;
@@ -104,6 +103,9 @@
     /** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */
     private String mProfileTypeOfProcessUser = null;
 
+    /** Whether the device is in headless system user mode; null until cached. */
+    private static Boolean sIsHeadlessSystemUser = null;
+
     /**
      * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
      * This type of user cannot be created; it can only pre-exist on first boot.
@@ -2068,28 +2070,20 @@
      * @return whether the device is running in a headless system user mode.
      */
     public static boolean isHeadlessSystemUserMode() {
-        final boolean realMode = RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
-        if (!Build.isDebuggable()) {
-            return realMode;
+        // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
+        // (Its value is determined when UMS is constructed and cannot change.)
+        // Worst case we might end up calling the AIDL method multiple times but that's fine.
+        if (sIsHeadlessSystemUser == null) {
+            // Unfortunately this API is static, but the property no longer is. So go fetch the UMS.
+            try {
+                final IUserManager service = IUserManager.Stub.asInterface(
+                        ServiceManager.getService(Context.USER_SERVICE));
+                sIsHeadlessSystemUser = service.isHeadlessSystemUserMode();
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
         }
-
-        final String emulatedMode = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
-        switch (emulatedMode) {
-            case SYSTEM_USER_MODE_EMULATION_FULL:
-                if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as false");
-                return false;
-            case SYSTEM_USER_MODE_EMULATION_HEADLESS:
-                if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as true");
-                return true;
-            case SYSTEM_USER_MODE_EMULATION_DEFAULT:
-            case "": // property not set
-                return realMode;
-            default:
-                Log.wtf(TAG, "isHeadlessSystemUserMode(): invalid value of property "
-                        + SYSTEM_USER_MODE_EMULATION_PROPERTY + " (" + emulatedMode + "); using"
-                                + " default value (headless=" + realMode + ")");
-                return realMode;
-        }
+        return sIsHeadlessSystemUser;
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 897b7c3..fab6f7b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9876,6 +9876,13 @@
                 "fingerprint_side_fps_auth_downtime";
 
         /**
+         * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+         * @hide
+         */
+        public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
+                "sfps_require_screen_on_to_auth_enabled";
+
+        /**
          * Whether or not debugging is enabled.
          * @hide
          */
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index 559b1ca..e330d1e 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -35,18 +35,22 @@
 public final class CreateCredentialResponse implements Parcelable {
     private final @Nullable CharSequence mHeader;
     private final @NonNull List<SaveEntry> mSaveEntries;
+    private final @Nullable Action mRemoteSaveEntry;
+    //TODO : Add actions if needed
 
     private CreateCredentialResponse(@NonNull Parcel in) {
         mHeader = in.readCharSequence();
         List<SaveEntry> saveEntries = new ArrayList<>();
         in.readTypedList(saveEntries, SaveEntry.CREATOR);
         mSaveEntries = saveEntries;
+        mRemoteSaveEntry = in.readTypedObject(Action.CREATOR);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeCharSequence(mHeader);
         dest.writeTypedList(mSaveEntries);
+        dest.writeTypedObject(mRemoteSaveEntry, flags);
     }
 
     @Override
@@ -69,11 +73,13 @@
 
     /* package-private */ CreateCredentialResponse(
             @Nullable CharSequence header,
-            @NonNull List<SaveEntry> saveEntries) {
+            @NonNull List<SaveEntry> saveEntries,
+            @Nullable Action remoteSaveEntry) {
         this.mHeader = header;
         this.mSaveEntries = saveEntries;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mSaveEntries);
+        this.mRemoteSaveEntry = remoteSaveEntry;
     }
 
     /** Returns the header to be displayed on the UI. */
@@ -86,6 +92,11 @@
         return mSaveEntries;
     }
 
+    /** Returns the remote save entry to be displayed on the UI. */
+    public @NonNull Action getRemoteSaveEntry() {
+        return mRemoteSaveEntry;
+    }
+
     /**
      * A builder for {@link CreateCredentialResponse}
      */
@@ -94,6 +105,7 @@
 
         private @Nullable CharSequence mHeader;
         private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
+        private @Nullable Action mRemoteSaveEntry;
 
         /** Sets the header to be displayed on the UI. */
         public @NonNull Builder setHeader(@Nullable CharSequence header) {
@@ -126,6 +138,14 @@
         }
 
         /**
+         * Sets a remote save entry to be shown on the UI.
+         */
+        public @NonNull Builder setRemoteSaveEntry(@Nullable Action remoteSaveEntry) {
+            mRemoteSaveEntry = remoteSaveEntry;
+            return this;
+        }
+
+        /**
          * Builds the instance.
          *
          * @throws IllegalArgumentException If {@code saveEntries} is empty.
@@ -135,7 +155,8 @@
                     + "not be empty");
             return new CreateCredentialResponse(
                     mHeader,
-                    mSaveEntries);
+                    mSaveEntries,
+                    mRemoteSaveEntry);
         }
     }
 }
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index 2cce169..ab5b524 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -43,12 +43,17 @@
     /** List of provider actions to be displayed on the UI. */
     private final @NonNull List<Action> mActions;
 
+    /** Remote credential entry to get the response from a different device. */
+    private final @Nullable Action mRemoteCredentialEntry;
+
     private CredentialsDisplayContent(@Nullable CharSequence header,
             @NonNull List<CredentialEntry> credentialEntries,
-            @NonNull List<Action> actions) {
+            @NonNull List<Action> actions,
+            @Nullable Action remoteCredentialEntry) {
         mHeader = header;
         mCredentialEntries = credentialEntries;
         mActions = actions;
+        mRemoteCredentialEntry = remoteCredentialEntry;
     }
 
     private CredentialsDisplayContent(@NonNull Parcel in) {
@@ -59,6 +64,7 @@
         List<Action> actions = new ArrayList<>();
         in.readTypedList(actions, Action.CREATOR);
         mActions = actions;
+        mRemoteCredentialEntry = in.readTypedObject(Action.CREATOR);
     }
 
     public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
@@ -84,6 +90,7 @@
         dest.writeCharSequence(mHeader);
         dest.writeTypedList(mCredentialEntries, flags);
         dest.writeTypedList(mActions, flags);
+        dest.writeTypedObject(mRemoteCredentialEntry, flags);
     }
 
     /**
@@ -108,12 +115,20 @@
     }
 
     /**
+     * Returns the remote credential entry to be displayed on the UI.
+     */
+    public @Nullable Action getRemoteCredentialEntry() {
+        return mRemoteCredentialEntry;
+    }
+
+    /**
      * Builds an instance of {@link CredentialsDisplayContent}.
      */
     public static final class Builder {
-        private CharSequence mHeader = null;
+        private CharSequence mHeader;
         private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
         private List<Action> mActions = new ArrayList<>();
+        private Action mRemoteCredentialEntry;
 
         /**
          * Sets the header to be displayed on the UI.
@@ -124,6 +139,14 @@
         }
 
         /**
+         * Sets the remote credential entry to be displayed on the UI.
+         */
+        public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
+            mRemoteCredentialEntry = remoteCredentialEntry;
+            return this;
+        }
+
+        /**
          * Adds a {@link CredentialEntry} to the list of entries to be displayed on
          * the UI.
          *
@@ -185,7 +208,8 @@
                 throw new IllegalStateException("credentialEntries and actions must not both "
                         + "be empty");
             }
-            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions);
+            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions,
+                    mRemoteCredentialEntry);
         }
     }
 }
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 1148120..6f8c5db 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -224,7 +224,7 @@
         byte[] resultBytes = messageDigest.digest();
 
         if (separator == null) {
-            return HexEncoding.encodeToString(resultBytes, true);
+            return HexEncoding.encodeToString(resultBytes, false);
         }
 
         int length = resultBytes.length;
diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java
index 524bb4c..719c614 100644
--- a/core/java/android/view/HandwritingDelegateConfiguration.java
+++ b/core/java/android/view/HandwritingDelegateConfiguration.java
@@ -47,7 +47,7 @@
      * @param delegatorViewId identifier of the delegator editor view for which handwriting mode
      *     should be initiated
      * @param initiationCallback callback called when a stylus {@link MotionEvent} occurs within
-     *     this view's bounds
+     *     this view's bounds. This will be called from the UI thread.
      */
     public HandwritingDelegateConfiguration(
             @IdRes int delegatorViewId, @NonNull Runnable initiationCallback) {
@@ -65,7 +65,7 @@
 
     /**
      * Returns the callback which should be called when a stylus {@link MotionEvent} occurs within
-     * the delegate view's bounds.
+     * the delegate view's bounds. The callback should only be called from the UI thread.
      */
     @NonNull
     public Runnable getInitiationCallback() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 49d9e67..2b071db 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17434,7 +17434,7 @@
      * (but after its own view has been drawn).
      * @param canvas the canvas on which to draw the view
      */
-    protected void dispatchDraw(Canvas canvas) {
+    protected void dispatchDraw(@NonNull Canvas canvas) {
 
     }
 
@@ -20719,7 +20719,7 @@
         out.bottom = mScrollY + mBottom - mTop;
     }
 
-    private void onDrawScrollIndicators(Canvas c) {
+    private void onDrawScrollIndicators(@NonNull Canvas c) {
         if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) {
             // No scroll indicators enabled.
             return;
@@ -20903,7 +20903,7 @@
      *
      * @see #awakenScrollBars(int)
      */
-    protected final void onDrawScrollBars(Canvas canvas) {
+    protected final void onDrawScrollBars(@NonNull Canvas canvas) {
         // scrollbars are drawn only when the animation is running
         final ScrollabilityCache cache = mScrollCache;
 
@@ -21015,7 +21015,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
+    protected void onDrawHorizontalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
             int l, int t, int r, int b) {
         scrollBar.setBounds(l, t, r, b);
         scrollBar.draw(canvas);
@@ -21035,7 +21035,7 @@
      * @hide
      */
     @UnsupportedAppUsage
-    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+    protected void onDrawVerticalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
             int l, int t, int r, int b) {
         scrollBar.setBounds(l, t, r, b);
         scrollBar.draw(canvas);
@@ -21046,7 +21046,7 @@
      *
      * @param canvas the canvas on which the background will be drawn
      */
-    protected void onDraw(Canvas canvas) {
+    protected void onDraw(@NonNull Canvas canvas) {
     }
 
     /*
@@ -23161,7 +23161,7 @@
      *
      * @hide
      */
-    protected final boolean drawsWithRenderNode(Canvas canvas) {
+    protected final boolean drawsWithRenderNode(@NonNull Canvas canvas) {
         return mAttachInfo != null
                 && mAttachInfo.mHardwareAccelerated
                 && canvas.isHardwareAccelerated();
@@ -23173,7 +23173,7 @@
      * This is where the View specializes rendering behavior based on layer type,
      * and hardware acceleration.
      */
-    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+    boolean draw(@NonNull Canvas canvas, ViewGroup parent, long drawingTime) {
 
         final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
 
@@ -23461,7 +23461,7 @@
         return (int) (dips * scale + 0.5f);
     }
 
-    final private void debugDrawFocus(Canvas canvas) {
+    private void debugDrawFocus(@NonNull Canvas canvas) {
         if (isFocused()) {
             final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
             final int l = mScrollX;
@@ -23496,7 +23496,7 @@
      * @param canvas The Canvas to which the View is rendered.
      */
     @CallSuper
-    public void draw(Canvas canvas) {
+    public void draw(@NonNull Canvas canvas) {
         final int privateFlags = mPrivateFlags;
         mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
 
@@ -23731,7 +23731,7 @@
      * @param canvas Canvas on which to draw the background
      */
     @UnsupportedAppUsage
-    private void drawBackground(Canvas canvas) {
+    private void drawBackground(@NonNull Canvas canvas) {
         final Drawable background = mBackground;
         if (background == null) {
             return;
@@ -24631,7 +24631,7 @@
      * Draw the default focus highlight onto the canvas if there is one and this view is focused.
      * @param canvas the canvas where we're drawing the highlight.
      */
-    private void drawDefaultFocusHighlight(Canvas canvas) {
+    private void drawDefaultFocusHighlight(@NonNull Canvas canvas) {
         if (mDefaultFocusHighlight != null && isFocused()) {
             if (mDefaultFocusHighlightSizeChanged) {
                 mDefaultFocusHighlightSizeChanged = false;
@@ -25429,7 +25429,7 @@
      *
      * @param canvas canvas to draw into
      */
-    public void onDrawForeground(Canvas canvas) {
+    public void onDrawForeground(@NonNull Canvas canvas) {
         onDrawScrollIndicators(canvas);
         onDrawScrollBars(canvas);
 
@@ -27487,7 +27487,7 @@
          *
          * @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
          */
-        public void onDrawShadow(Canvas canvas) {
+        public void onDrawShadow(@NonNull Canvas canvas) {
             final View view = mView.get();
             if (view != null) {
                 view.draw(canvas);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c4f20dc..46b2cfc 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4166,7 +4166,7 @@
     /**
      * @hide
      */
-    protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
+    protected void onDebugDrawMargins(@NonNull Canvas canvas, Paint paint) {
         for (int i = 0; i < getChildCount(); i++) {
             View c = getChildAt(i);
             c.getLayoutParams().onDebugDraw(c, canvas, paint);
@@ -4176,7 +4176,7 @@
     /**
      * @hide
      */
-    protected void onDebugDraw(Canvas canvas) {
+    protected void onDebugDraw(@NonNull Canvas canvas) {
         Paint paint = getDebugPaint();
 
         // Draw optical bounds
@@ -4224,7 +4224,7 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
+    protected void dispatchDraw(@NonNull Canvas canvas) {
         final int childrenCount = mChildrenCount;
         final View[] children = mChildren;
         int flags = mGroupFlags;
@@ -4533,7 +4533,7 @@
      * @param drawingTime The time at which draw is occurring
      * @return True if an invalidate() was issued
      */
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+    protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) {
         return child.draw(canvas, this, drawingTime);
     }
 
@@ -9208,7 +9208,8 @@
         }
     }
 
-    private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+    private static void drawRect(@NonNull Canvas canvas, Paint paint, int x1, int y1,
+                                 int x2, int y2) {
         if (sDebugLines== null) {
             // TODO: This won't work with multiple UI threads in a single process
             sDebugLines = new float[16];
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index a8ed96e..2d974db 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -20,12 +20,14 @@
 import android.annotation.Nullable;
 import android.graphics.Matrix;
 import android.graphics.RectF;
+import android.inputmethodservice.InputMethodService;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.Layout;
 import android.text.SpannedString;
 import android.text.TextUtils;
 import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
+import android.widget.TextView;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -110,7 +112,7 @@
     /**
      * Container of rectangular position of Editor in the local coordinates that will be transformed
      * with the transformation matrix when rendered on the screen.
-     * @see {@link EditorBoundsInfo}.
+     * @see EditorBoundsInfo
      */
     private final EditorBoundsInfo mEditorBoundsInfo;
 
@@ -122,6 +124,12 @@
     private final float[] mMatrixValues;
 
     /**
+     * Information about text appearance in the editor for use by {@link InputMethodService}.
+     */
+    @Nullable
+    private final TextAppearanceInfo mTextAppearanceInfo;
+
+    /**
      * A list of visible line bounds stored in a float array. This array is divided into segment of
      * four where each element in the segment represents left, top, right respectively and bottom
      * of the line bounds.
@@ -157,10 +165,11 @@
         mInsertionMarkerTop = source.readFloat();
         mInsertionMarkerBaseline = source.readFloat();
         mInsertionMarkerBottom = source.readFloat();
-        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class);
+        mCharacterBoundsArray = source.readTypedObject(SparseRectFArray.CREATOR);
         mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR);
         mMatrixValues = source.createFloatArray();
         mVisibleLineBounds = source.createFloatArray();
+        mTextAppearanceInfo = source.readTypedObject(TextAppearanceInfo.CREATOR);
     }
 
     /**
@@ -181,10 +190,11 @@
         dest.writeFloat(mInsertionMarkerTop);
         dest.writeFloat(mInsertionMarkerBaseline);
         dest.writeFloat(mInsertionMarkerBottom);
-        dest.writeParcelable(mCharacterBoundsArray, flags);
+        dest.writeTypedObject(mCharacterBoundsArray, flags);
         dest.writeTypedObject(mEditorBoundsInfo, flags);
         dest.writeFloatArray(mMatrixValues);
         dest.writeFloatArray(mVisibleLineBounds);
+        dest.writeTypedObject(mTextAppearanceInfo, flags);
     }
 
     @Override
@@ -262,6 +272,11 @@
                 return false;
             }
         }
+
+        if (!Objects.equals(mTextAppearanceInfo, that.mTextAppearanceInfo)) {
+            return false;
+        }
+
         return true;
     }
 
@@ -270,16 +285,17 @@
         return "CursorAnchorInfo{mHashCode=" + mHashCode
                 + " mSelection=" + mSelectionStart + "," + mSelectionEnd
                 + " mComposingTextStart=" + mComposingTextStart
-                + " mComposingText=" + Objects.toString(mComposingText)
+                + " mComposingText=" + mComposingText
                 + " mInsertionMarkerFlags=" + mInsertionMarkerFlags
                 + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
                 + " mInsertionMarkerTop=" + mInsertionMarkerTop
                 + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
                 + " mInsertionMarkerBottom=" + mInsertionMarkerBottom
-                + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray)
+                + " mCharacterBoundsArray=" + mCharacterBoundsArray
                 + " mEditorBoundsInfo=" + mEditorBoundsInfo
                 + " mVisibleLineBounds=" + getVisibleLineBounds()
                 + " mMatrix=" + Arrays.toString(mMatrixValues)
+                + " mTextAppearanceInfo=" + mTextAppearanceInfo
                 + "}";
     }
 
@@ -303,6 +319,7 @@
         private boolean mMatrixInitialized = false;
         private float[] mVisibleLineBounds = new float[LINE_BOUNDS_INITIAL_SIZE * 4];
         private int mVisibleLineBoundsCount = 0;
+        private TextAppearanceInfo mTextAppearanceInfo = null;
 
         /**
          * Sets the text range of the selection. Calling this can be skipped if there is no
@@ -416,6 +433,17 @@
         }
 
         /**
+         * Set the information related to text appearance, which is extracted from the original
+         * {@link TextView}.
+         * @param textAppearanceInfo {@link TextAppearanceInfo} of TextView.
+         */
+        @NonNull
+        public Builder setTextAppearanceInfo(@Nullable TextAppearanceInfo textAppearanceInfo) {
+            mTextAppearanceInfo = textAppearanceInfo;
+            return this;
+        }
+
+        /**
          * Add the bounds of a visible text line of the current editor.
          *
          * The line bounds should not include the vertical space between lines or the horizontal
@@ -504,6 +532,7 @@
             }
             mEditorBoundsInfo = null;
             clearVisibleLineBounds();
+            mTextAppearanceInfo = null;
         }
     }
 
@@ -524,7 +553,8 @@
                 builder.mInsertionMarkerHorizontal, builder.mInsertionMarkerTop,
                 builder.mInsertionMarkerBaseline, builder.mInsertionMarkerBottom,
                 characterBoundsArray, builder.mEditorBoundsInfo, matrixValues,
-                Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount));
+                Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount),
+                builder.mTextAppearanceInfo);
     }
 
     private CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart,
@@ -533,7 +563,8 @@
             float insertionMarkerBaseline, float insertionMarkerBottom,
             @Nullable SparseRectFArray characterBoundsArray,
             @Nullable EditorBoundsInfo editorBoundsInfo,
-            @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds) {
+            @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds,
+            @Nullable TextAppearanceInfo textAppearanceInfo) {
         mSelectionStart = selectionStart;
         mSelectionEnd = selectionEnd;
         mComposingTextStart = composingTextStart;
@@ -547,6 +578,7 @@
         mEditorBoundsInfo = editorBoundsInfo;
         mMatrixValues = matrixValues;
         mVisibleLineBounds = visibleLineBounds;
+        mTextAppearanceInfo = textAppearanceInfo;
 
         // To keep hash function simple, we only use some complex objects for hash.
         int hashCode = Objects.hashCode(mComposingText);
@@ -573,7 +605,7 @@
                 original.mInsertionMarkerTop, original.mInsertionMarkerBaseline,
                 original.mInsertionMarkerBottom, original.mCharacterBoundsArray,
                 original.mEditorBoundsInfo, computeMatrixValues(parentMatrix, original),
-                original.mVisibleLineBounds);
+                original.mVisibleLineBounds, original.mTextAppearanceInfo);
     }
 
     /**
@@ -741,6 +773,16 @@
     }
 
     /**
+     * Returns {@link TextAppearanceInfo} for the current editor, or {@code null} if IME is not
+     * subscribed with {@link InputConnection#CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}
+     * or {@link InputConnection#CURSOR_UPDATE_MONITOR}.
+     */
+    @Nullable
+    public TextAppearanceInfo getTextAppearanceInfo() {
+        return mTextAppearanceInfo;
+    }
+
+    /**
      * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
      * matrix that is to be applied other positional data in this class.
      * @return a new instance (copy) of the transformation matrix.
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 1afa987..a66c67b 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -107,8 +107,8 @@
      * @param where where the information is coming from.
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresNoPermission
     @AnyThread
+    @RequiresNoPermission
     static void startProtoDump(byte[] protoDump, int source, String where,
             @Nullable Consumer<RemoteException> exceptionHandler) {
         final IInputMethodManager service = getService();
@@ -127,8 +127,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
     static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -146,8 +146,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
     static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -165,8 +165,8 @@
      *
      * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
      */
-    @RequiresNoPermission
     @AnyThread
+    @RequiresNoPermission
     static boolean isImeTraceEnabled() {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -182,8 +182,8 @@
     /**
      * Invokes {@link IInputMethodManager#removeImeSurface()}
      */
-    @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     @AnyThread
+    @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -212,6 +212,7 @@
 
     @AnyThread
     @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness) {
         final IInputMethodManager service = getService();
@@ -227,6 +228,7 @@
 
     @AnyThread
     @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -241,6 +243,7 @@
 
     @AnyThread
     @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable String imiId,
             boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
         final IInputMethodManager service = getService();
@@ -257,6 +260,7 @@
 
     @AnyThread
     @Nullable
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -302,6 +306,7 @@
 
     @AnyThread
     @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason,
             @NonNull IInputMethodClient client, @Nullable IBinder windowToken,
             @StartInputFlags int startInputFlags,
@@ -340,14 +345,14 @@
     }
 
     @AnyThread
-    static void showInputMethodPickerFromSystem(@NonNull IInputMethodClient client,
-            int auxiliarySubtypeMode, int displayId) {
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return;
         }
         try {
-            service.showInputMethodPickerFromSystem(client, auxiliarySubtypeMode, displayId);
+            service.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -369,6 +374,7 @@
 
     @AnyThread
     @Nullable
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -382,6 +388,7 @@
     }
 
     @AnyThread
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static void setAdditionalInputMethodSubtypes(@NonNull String imeId,
             @NonNull InputMethodSubtype[] subtypes, @UserIdInt int userId) {
         final IInputMethodManager service = getService();
@@ -396,6 +403,7 @@
     }
 
     @AnyThread
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imeId,
             @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
         final IInputMethodManager service = getService();
@@ -476,6 +484,7 @@
     }
 
     @AnyThread
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -503,6 +512,7 @@
     }
 
     @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     static void setStylusWindowIdleTimeoutForTest(
             IInputMethodClient client, @DurationMillisLong long timeout) {
         final IInputMethodManager service = getService();
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index d6d7339..2099a31 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1057,8 +1057,10 @@
      * used together with {@link #CURSOR_UPDATE_MONITOR}.
      * <p>
      * Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS} and
-     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} are included but specifying them can
+     * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, and
+     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, are included but specifying them can
      * filter-out others.
      * It can be CPU intensive to include all, filtering specific info is recommended.
      * </p>
@@ -1076,7 +1078,8 @@
      * <p>
      * Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
      * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS} and
+     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, and
      * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, are included but specifying them can
      * filter-out others.
      * It can be CPU intensive to include all, filtering specific info is recommended.
@@ -1092,6 +1095,7 @@
      * <p>
      * This flag can be used together with filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
      * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE},
      * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags
      * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
      * </p>
@@ -1107,8 +1111,8 @@
      * <p>
      * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
      * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags
-     * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}
+     * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
      * </p>
      */
     int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 1 << 3;
@@ -1123,8 +1127,8 @@
      * <p>
      * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
      * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS} and update flags {@link #CURSOR_UPDATE_IMMEDIATE}
-     * and {@link #CURSOR_UPDATE_MONITOR}.
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}
+     * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
      * </p>
      */
     int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 1 << 4;
@@ -1138,14 +1142,29 @@
      * {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
      * <p>
      * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}
-     * and update flags {@link #CURSOR_UPDATE_IMMEDIATE}
-     * and {@link #CURSOR_UPDATE_MONITOR}.
+     * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE} and update flags
+     * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
      * </p>
      */
     int CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS = 1 << 5;
 
     /**
+     * The editor is requested to call
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+     * with new text appearance info {@link CursorAnchorInfo#getTextAppearanceInfo()}}
+     * whenever cursor/anchor position is changed. To disable monitoring, call
+     * {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
+     * <p>
+     * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS} and update flags
+     * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
+     * </p>
+     */
+    int CURSOR_UPDATE_FILTER_TEXT_APPEARANCE = 1 << 6;
+
+    /**
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -1158,7 +1177,8 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {CURSOR_UPDATE_FILTER_EDITOR_BOUNDS, CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS,
-            CURSOR_UPDATE_FILTER_INSERTION_MARKER, CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+            CURSOR_UPDATE_FILTER_INSERTION_MARKER, CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS,
+            CURSOR_UPDATE_FILTER_TEXT_APPEARANCE},
             flag = true, prefix = { "CURSOR_UPDATE_FILTER_" })
     @interface CursorUpdateFilter{}
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9106ce2..d08816b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -16,8 +16,6 @@
 
 package android.view.inputmethod;
 
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -128,6 +126,7 @@
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
 /**
@@ -1525,12 +1524,18 @@
     /**
      * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled for
      * the given userId.
-     * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
-     * called and Stylus touch should continue as normal touch input.
+     *
+     * <p>If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
+     * called and Stylus touch should continue as normal touch input.</p>
+     *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
+     *
      * @see #startStylusHandwriting(View)
      * @param userId user ID to query.
      * @hide
      */
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
         final Context fallbackContext = ActivityThread.currentApplication();
         if (fallbackContext == null) {
@@ -1549,13 +1554,16 @@
     /**
      * Returns the list of installed input methods for the specified user.
      *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
+     *
      * @param userId user ID to query
      * @return {@link List} of {@link InputMethodInfo}.
      * @hide
      */
     @TestApi
-    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
     @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
         return IInputMethodManagerGlobalInvoker.getInputMethodList(userId,
                 DirectBootAwareness.AUTO);
@@ -1564,14 +1572,17 @@
     /**
      * Returns the list of installed input methods for the specified user.
      *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
+     *
      * @param userId user ID to query
      * @param directBootAwareness {@code true} if caller want to query installed input methods list
      * on user locked state.
      * @return {@link List} of {@link InputMethodInfo}.
      * @hide
      */
-    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
     @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness) {
         return IInputMethodManagerGlobalInvoker.getInputMethodList(userId, directBootAwareness);
@@ -1595,11 +1606,14 @@
     /**
      * Returns the list of enabled input methods for the specified user.
      *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
+     *
      * @param userId user ID to query
      * @return {@link List} of {@link InputMethodInfo}.
      * @hide
      */
-    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
         return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(userId);
     }
@@ -2352,7 +2366,11 @@
      * Starts an input connection from the served view that gains the window focus.
      * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
      * background thread may blocked by other methods which already inside {@code mH} lock.
+     *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
      */
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     private boolean startInputInner(@StartInputReason int startInputReason,
             @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
             @SoftInputModeFlags int softInputMode, int windowFlags) {
@@ -2609,8 +2627,8 @@
      * @param timeout to set in milliseconds. To reset to default, use a value <= zero.
      * @hide
      */
-    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     public void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) {
         synchronized (mH) {
             IInputMethodManagerGlobalInvoker.setStylusWindowIdleTimeoutForTest(mClient, timeout);
@@ -3089,7 +3107,7 @@
      *
      * <p>On Android {@link Build.VERSION_CODES#Q} and later devices, the undocumented behavior that
      * token can be {@code null} when the caller has
-     * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update
+     * {@link Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update
      * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
      * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
      *
@@ -3121,7 +3139,7 @@
             if (fallbackContext == null) {
                 return;
             }
-            if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+            if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
                     != PackageManager.PERMISSION_GRANTED) {
                 return;
             }
@@ -3158,7 +3176,7 @@
      * from an application or a service which has a token of the currently active input method.
      *
      * <p>On Android {@link Build.VERSION_CODES#Q} and later devices, {@code token} cannot be
-     * {@code null} even with {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}. Instead,
+     * {@code null} even with {@link Manifest.permission#WRITE_SECURE_SETTINGS}. Instead,
      * update {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
      * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
      *
@@ -3440,12 +3458,12 @@
      * @param displayId The ID of the display where the chooser dialog should be shown.
      * @hide
      */
-    @RequiresPermission(WRITE_SECURE_SETTINGS)
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     public void showInputMethodPickerFromSystem(boolean showAuxiliarySubtypes, int displayId) {
         final int mode = showAuxiliarySubtypes
                 ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
                 : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
-        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mClient, mode, displayId);
+        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mode, displayId);
     }
 
     @GuardedBy("mH")
@@ -3519,11 +3537,11 @@
      *             {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
      *             does not require any permission as long as the caller is the current IME.
      *             If the calling process is some privileged app that already has
-     *             {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
+     *             {@link Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
      *             directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
      */
     @Deprecated
-    @RequiresPermission(WRITE_SECURE_SETTINGS)
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
         if (Process.myUid() == Process.SYSTEM_UID) {
             Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
@@ -3540,7 +3558,7 @@
         if (fallbackContext == null) {
             return false;
         }
-        if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+        if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
                 != PackageManager.PERMISSION_GRANTED) {
             return false;
         }
@@ -3622,6 +3640,32 @@
     }
 
     /**
+     * {@code true} means that
+     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
+     * {@code false} when the IME client and the IME run in different displays.
+     */
+    final AtomicBoolean mRequestCursorUpdateDisplayIdCheck = new AtomicBoolean(true);
+
+    /**
+     * Controls the display ID mismatch validation in
+     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)}.
+     *
+     * <p>{@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} is not guaranteed to work
+     * correctly when the IME client and the IME run in different displays.  This is why
+     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
+     * {@code false} by default when the display ID does not match. This method allows special apps
+     * to override this behavior when they are sure that it should work.</p>
+     *
+     * <p>By default the validation is enabled.</p>
+     *
+     * @param enabled {@code false} to disable the display ID validation.
+     * @hide
+     */
+    public void setRequestCursorUpdateDisplayIdCheck(boolean enabled) {
+        mRequestCursorUpdateDisplayIdCheck.set(enabled);
+    }
+
+    /**
      * An internal API for {@link android.hardware.display.VirtualDisplay} to report where its
      * embedded virtual display is placed.
      *
diff --git a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
index 63d9167..5df9fd1 100644
--- a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
+++ b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.Manifest;
 import android.annotation.AnyThread;
 import android.annotation.Nullable;
 import android.annotation.RequiresNoPermission;
@@ -51,8 +52,8 @@
      * @param where where the information is coming from.
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresNoPermission
     @AnyThread
+    @RequiresNoPermission
     public static void startProtoDump(byte[] protoDump, int source, String where,
             @Nullable Consumer<RemoteException> exceptionHandler) {
         IInputMethodManagerGlobalInvoker.startProtoDump(protoDump, source, where, exceptionHandler);
@@ -63,8 +64,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
     public static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
         IInputMethodManagerGlobalInvoker.startImeTrace(exceptionHandler);
     }
@@ -74,8 +75,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
     public static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
         IInputMethodManagerGlobalInvoker.stopImeTrace(exceptionHandler);
     }
@@ -85,8 +86,8 @@
      *
      * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
      */
-    @RequiresNoPermission
     @AnyThread
+    @RequiresNoPermission
     public static boolean isImeTraceEnabled() {
         return IInputMethodManagerGlobalInvoker.isImeTraceEnabled();
     }
@@ -96,8 +97,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     @AnyThread
+    @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     public static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
         IInputMethodManagerGlobalInvoker.removeImeSurface(exceptionHandler);
     }
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index e6b22de..c01c310 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -1050,7 +1050,8 @@
             Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
             return false;
         }
-        if (mParentInputMethodManager.getDisplayId() != imeDisplayId
+        if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
+                && mParentInputMethodManager.getDisplayId() != imeDisplayId
                 && !mParentInputMethodManager.hasVirtualDisplayToScreenMatrix()) {
             // requestCursorUpdates() is not currently supported across displays.
             return false;
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
new file mode 100644
index 0000000..1df4fc5
--- /dev/null
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.ColorInt;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
+import android.content.res.ColorStateList;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
+import android.graphics.text.LineBreakConfig;
+import android.inputmethodservice.InputMethodService;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputFilter;
+import android.widget.TextView;
+
+import java.util.Objects;
+
+/**
+ * Information about text appearance in an editor, passed through
+ * {@link CursorAnchorInfo} for use by {@link InputMethodService}.
+ *
+ * @see TextView
+ * @see Paint
+ * @see CursorAnchorInfo.Builder#setTextAppearanceInfo(TextAppearanceInfo)
+ * @see CursorAnchorInfo#getTextAppearanceInfo()
+ */
+public final class TextAppearanceInfo implements Parcelable {
+    /**
+     * The text size (in pixels) for current {@link TextView}.
+     */
+    private final @Px float mTextSize;
+
+    /**
+     * The LocaleList of the text.
+     */
+    @NonNull private final LocaleList mTextLocales;
+
+    /**
+     * The font family name if the {@link Typeface} of the text is created from a system font
+     * family, otherwise this value should be null.
+     */
+    @Nullable private final String mSystemFontFamilyName;
+
+    /**
+     * The weight of the text.
+     */
+    private final @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int mTextFontWeight;
+
+    /**
+     * The style (normal, bold, italic, bold|italic) of the text, see {@link Typeface}.
+     */
+    private final @Typeface.Style int mTextStyle;
+
+    /**
+     * Whether the transformation method applied to the current {@link TextView} is set to
+     * ALL CAPS.
+     */
+    private final boolean mAllCaps;
+
+    /**
+     * The horizontal offset (in pixels) of the text shadow.
+     */
+    private final @Px float mShadowDx;
+
+    /**
+     * The vertical offset (in pixels) of the text shadow.
+     */
+    private final @Px float mShadowDy;
+
+    /**
+     * The blur radius (in pixels) of the text shadow.
+     */
+    private final @Px float mShadowRadius;
+
+    /**
+     * The elegant text height, especially for less compacted complex script text.
+     */
+    private final boolean mElegantTextHeight;
+
+    /**
+     * Whether to expand linespacing based on fallback fonts.
+     */
+    private final boolean mFallbackLineSpacing;
+
+    /**
+     * The text letter-spacing (in ems), which determines the spacing between characters.
+     */
+    private final float mLetterSpacing;
+
+    /**
+     * The font feature settings.
+     */
+    @Nullable private final String mFontFeatureSettings;
+
+    /**
+     * The font variation settings.
+     */
+    @Nullable private final String mFontVariationSettings;
+
+    /**
+     * The line-break strategies for text wrapping.
+     */
+    private final @LineBreakConfig.LineBreakStyle int mLineBreakStyle;
+
+    /**
+     * The line-break word strategies for text wrapping.
+     */
+    private final @LineBreakConfig.LineBreakWordStyle int mLineBreakWordStyle;
+
+    /**
+     * The extent by which text should be stretched horizontally. Returns 1.0 if not specified.
+     */
+    private final float mTextScaleX;
+
+    /**
+     * The color of the text selection highlight.
+     */
+    private final @ColorInt int mTextColorHighlight;
+
+    /**
+     * The current text color.
+     */
+    private final @ColorInt int mTextColor;
+
+    /**
+     * The current color of the hint text.
+     */
+    private final @ColorInt int mTextColorHint;
+
+    /**
+     * The text color for links.
+     */
+    @Nullable private final ColorStateList mTextColorLink;
+
+    /**
+     * The max length of text.
+     */
+    private final int mMaxLength;
+
+
+    public TextAppearanceInfo(@NonNull TextView textView) {
+        mTextSize = textView.getTextSize();
+        mTextLocales = textView.getTextLocales();
+        Typeface typeface = textView.getPaint().getTypeface();
+        String systemFontFamilyName = null;
+        int textFontWeight = -1;
+        if (typeface != null) {
+            systemFontFamilyName = typeface.getSystemFontFamilyName();
+            textFontWeight = typeface.getWeight();
+        }
+        mSystemFontFamilyName = systemFontFamilyName;
+        mTextFontWeight = textFontWeight;
+        mTextStyle = textView.getTypefaceStyle();
+        mAllCaps = textView.isAllCaps();
+        mShadowRadius = textView.getShadowRadius();
+        mShadowDx = textView.getShadowDx();
+        mShadowDy = textView.getShadowDy();
+        mElegantTextHeight = textView.isElegantTextHeight();
+        mFallbackLineSpacing = textView.isFallbackLineSpacing();
+        mLetterSpacing = textView.getLetterSpacing();
+        mFontFeatureSettings = textView.getFontFeatureSettings();
+        mFontVariationSettings = textView.getFontVariationSettings();
+        mLineBreakStyle = textView.getLineBreakStyle();
+        mLineBreakWordStyle = textView.getLineBreakWordStyle();
+        mTextScaleX = textView.getTextScaleX();
+        mTextColorHighlight = textView.getHighlightColor();
+        mTextColor = textView.getCurrentTextColor();
+        mTextColorHint = textView.getCurrentHintTextColor();
+        mTextColorLink = textView.getLinkTextColors();
+        int maxLength = -1;
+        for (InputFilter filter: textView.getFilters()) {
+            if (filter instanceof InputFilter.LengthFilter) {
+                maxLength = ((InputFilter.LengthFilter) filter).getMax();
+                // There is at most one LengthFilter.
+                break;
+            }
+        }
+        mMaxLength = maxLength;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeFloat(mTextSize);
+        mTextLocales.writeToParcel(dest, flags); // NonNull
+        dest.writeBoolean(mAllCaps);
+        dest.writeString8(mSystemFontFamilyName);
+        dest.writeInt(mTextFontWeight);
+        dest.writeInt(mTextStyle);
+        dest.writeFloat(mShadowDx);
+        dest.writeFloat(mShadowDy);
+        dest.writeFloat(mShadowRadius);
+        dest.writeBoolean(mElegantTextHeight);
+        dest.writeBoolean(mFallbackLineSpacing);
+        dest.writeFloat(mLetterSpacing);
+        dest.writeString8(mFontFeatureSettings);
+        dest.writeString8(mFontVariationSettings);
+        dest.writeInt(mLineBreakStyle);
+        dest.writeInt(mLineBreakWordStyle);
+        dest.writeFloat(mTextScaleX);
+        dest.writeInt(mTextColorHighlight);
+        dest.writeInt(mTextColor);
+        dest.writeInt(mTextColorHint);
+        dest.writeTypedObject(mTextColorLink, flags);
+        dest.writeInt(mMaxLength);
+    }
+
+    private TextAppearanceInfo(@NonNull Parcel in) {
+        mTextSize = in.readFloat();
+        mTextLocales = LocaleList.CREATOR.createFromParcel(in);
+        mAllCaps = in.readBoolean();
+        mSystemFontFamilyName = in.readString8();
+        mTextFontWeight = in.readInt();
+        mTextStyle = in.readInt();
+        mShadowDx = in.readFloat();
+        mShadowDy = in.readFloat();
+        mShadowRadius = in.readFloat();
+        mElegantTextHeight = in.readBoolean();
+        mFallbackLineSpacing = in.readBoolean();
+        mLetterSpacing = in.readFloat();
+        mFontFeatureSettings = in.readString8();
+        mFontVariationSettings = in.readString8();
+        mLineBreakStyle = in.readInt();
+        mLineBreakWordStyle = in.readInt();
+        mTextScaleX = in.readFloat();
+        mTextColorHighlight = in.readInt();
+        mTextColor = in.readInt();
+        mTextColorHint = in.readInt();
+        mTextColorLink = in.readTypedObject(ColorStateList.CREATOR);
+        mMaxLength = in.readInt();
+    }
+
+    @NonNull
+    public static final Creator<TextAppearanceInfo> CREATOR = new Creator<TextAppearanceInfo>() {
+        @Override
+        public TextAppearanceInfo createFromParcel(@NonNull Parcel in) {
+            return new TextAppearanceInfo(in);
+        }
+
+        @Override
+        public TextAppearanceInfo[] newArray(int size) {
+            return new TextAppearanceInfo[size];
+        }
+    };
+
+    /**
+     * Returns the text size (in pixels) for current {@link TextView}.
+     */
+    public @Px float getTextSize() {
+        return mTextSize;
+    }
+
+    /**
+     * Returns the LocaleList of the text.
+     */
+    @NonNull
+    public LocaleList getTextLocales() {
+        return mTextLocales;
+    }
+
+    /**
+     * Returns the font family name if the {@link Typeface} of the text is created from a
+     * system font family. Returns null if no {@link Typeface} is specified, or it is not created
+     * from a system font family.
+     */
+    @Nullable
+    public String getFontFamilyName() {
+        return mSystemFontFamilyName;
+    }
+
+    /**
+     * Returns the weight of the text. Returns -1 when no {@link Typeface} is specified.
+     */
+    public @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int getTextFontWeight() {
+        return mTextFontWeight;
+    }
+
+    /**
+     * Returns the style (normal, bold, italic, bold|italic) of the text. Returns
+     * {@link Typeface#NORMAL} when no {@link Typeface} is specified. See {@link Typeface} for
+     * more information.
+     */
+    public @Typeface.Style int getTextStyle() {
+        return mTextStyle;
+    }
+
+    /**
+     * Returns whether the transformation method applied to the current {@link TextView} is set to
+     * ALL CAPS.
+     */
+    public boolean isAllCaps() {
+        return mAllCaps;
+    }
+
+    /**
+     * Returns the horizontal offset (in pixels) of the text shadow.
+     */
+    public @Px float getShadowDx() {
+        return mShadowDx;
+    }
+
+    /**
+     * Returns the vertical offset (in pixels) of the text shadow.
+     */
+    public @Px float getShadowDy() {
+        return mShadowDy;
+    }
+
+    /**
+     * Returns the blur radius (in pixels) of the text shadow.
+     */
+    public @Px float getShadowRadius() {
+        return mShadowRadius;
+    }
+
+    /**
+     * Returns {@code true} if the elegant height metrics flag is set. This setting selects font
+     * variants that have not been compacted to fit Latin-based vertical metrics, and also increases
+     * top and bottom bounds to provide more space.
+     */
+    public boolean isElegantTextHeight() {
+        return mElegantTextHeight;
+    }
+
+    /**
+     * Returns whether to expand linespacing based on fallback fonts.
+     *
+     * @see TextView#setFallbackLineSpacing(boolean)
+     */
+    public boolean isFallbackLineSpacing() {
+        return mFallbackLineSpacing;
+    }
+
+    /**
+     * Returns the text letter-spacing, which determines the spacing between characters.
+     * The value is in 'EM' units. Normally, this value is 0.0.
+     */
+    public float getLetterSpacing() {
+        return mLetterSpacing;
+    }
+
+    /**
+     * Returns the font feature settings. Returns null if not specified.
+     *
+     * @see Paint#getFontFeatureSettings()
+     */
+    @Nullable
+    public String getFontFeatureSettings() {
+        return mFontFeatureSettings;
+    }
+
+    /**
+     * Returns the font variation settings. Returns null if no variation is specified.
+     *
+     * @see Paint#getFontVariationSettings()
+     */
+    @Nullable
+    public String getFontVariationSettings() {
+        return mFontVariationSettings;
+    }
+
+    /**
+     * Returns the line-break strategies for text wrapping.
+     *
+     * @see TextView#setLineBreakStyle(int)
+     */
+    public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
+        return mLineBreakStyle;
+    }
+
+    /**
+     * Returns the line-break word strategies for text wrapping.
+     *
+     * @see TextView#setLineBreakWordStyle(int)
+     */
+    public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
+        return mLineBreakWordStyle;
+    }
+
+    /**
+     * Returns the extent by which text should be stretched horizontally. Returns 1.0 if not
+     * specified.
+     */
+    public float getTextScaleX() {
+        return mTextScaleX;
+    }
+
+    /**
+     * Returns the color of the text selection highlight.
+     */
+    public @ColorInt int getTextColorHighlight() {
+        return mTextColorHighlight;
+    }
+
+    /**
+     * Returns the current text color.
+     */
+    public @ColorInt int getTextColor() {
+        return mTextColor;
+    }
+
+    /**
+     * Returns the current color of the hint text.
+     */
+    public @ColorInt int getTextColorHint() {
+        return mTextColorHint;
+    }
+
+    /**
+     * Returns the text color for links.
+     */
+    @Nullable
+    public ColorStateList getTextColorLink() {
+        return mTextColorLink;
+    }
+
+    /**
+     * Returns the max length of text, which is used to set an input filter to constrain the text
+     * length to the specified number. Returns -1 when there is no {@link InputFilter.LengthFilter}
+     * in the Editor.
+     */
+    public int getMaxLength() {
+        return mMaxLength;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof TextAppearanceInfo)) return false;
+        TextAppearanceInfo that = (TextAppearanceInfo) o;
+        return Float.compare(that.mTextSize, mTextSize) == 0
+                && mTextFontWeight == that.mTextFontWeight && mTextStyle == that.mTextStyle
+                && mAllCaps == that.mAllCaps && Float.compare(that.mShadowDx, mShadowDx) == 0
+                && Float.compare(that.mShadowDy, mShadowDy) == 0 && Float.compare(
+                that.mShadowRadius, mShadowRadius) == 0 && mMaxLength == that.mMaxLength
+                && mElegantTextHeight == that.mElegantTextHeight
+                && mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare(
+                that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle
+                && mLineBreakWordStyle == that.mLineBreakWordStyle
+                && mTextColorHighlight == that.mTextColorHighlight && mTextColor == that.mTextColor
+                && mTextColorLink.getDefaultColor() == that.mTextColorLink.getDefaultColor()
+                && mTextColorHint == that.mTextColorHint && Objects.equals(
+                mTextLocales, that.mTextLocales) && Objects.equals(mSystemFontFamilyName,
+                that.mSystemFontFamilyName) && Objects.equals(mFontFeatureSettings,
+                that.mFontFeatureSettings) && Objects.equals(mFontVariationSettings,
+                that.mFontVariationSettings) && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTextSize, mTextLocales, mSystemFontFamilyName, mTextFontWeight,
+                mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mElegantTextHeight,
+                mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, mFontVariationSettings,
+                mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, mTextColorHighlight, mTextColor,
+                mTextColorHint, mTextColorLink, mMaxLength);
+    }
+
+    @Override
+    public String toString() {
+        return "TextAppearanceInfo{"
+                + "mTextSize=" + mTextSize
+                + ", mTextLocales=" + mTextLocales
+                + ", mSystemFontFamilyName='" + mSystemFontFamilyName + '\''
+                + ", mTextFontWeight=" + mTextFontWeight
+                + ", mTextStyle=" + mTextStyle
+                + ", mAllCaps=" + mAllCaps
+                + ", mShadowDx=" + mShadowDx
+                + ", mShadowDy=" + mShadowDy
+                + ", mShadowRadius=" + mShadowRadius
+                + ", mElegantTextHeight=" + mElegantTextHeight
+                + ", mFallbackLineSpacing=" + mFallbackLineSpacing
+                + ", mLetterSpacing=" + mLetterSpacing
+                + ", mFontFeatureSettings='" + mFontFeatureSettings + '\''
+                + ", mFontVariationSettings='" + mFontVariationSettings + '\''
+                + ", mLineBreakStyle=" + mLineBreakStyle
+                + ", mLineBreakWordStyle=" + mLineBreakWordStyle
+                + ", mTextScaleX=" + mTextScaleX
+                + ", mTextColorHighlight=" + mTextColorHighlight
+                + ", mTextColor=" + mTextColor
+                + ", mTextColorHint=" + mTextColorHint
+                + ", mTextColorLink=" + mTextColorLink
+                + ", mMaxLength=" + mMaxLength
+                + '}';
+    }
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8f590f8..56524a2 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -125,6 +125,7 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.TextAppearanceInfo;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationManager;
 import android.widget.AdapterView.OnItemClickListener;
@@ -4630,14 +4631,17 @@
                     (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
             boolean includeVisibleLineBounds =
                     (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
+            boolean includeTextAppearance =
+                    (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
             boolean includeAll =
                     (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
-                    && !includeVisibleLineBounds);
+                    && !includeVisibleLineBounds && !includeTextAppearance);
 
             includeEditorBounds |= includeAll;
             includeCharacterBounds |= includeAll;
             includeInsertionMarker |= includeAll;
             includeVisibleLineBounds |= includeAll;
+            includeTextAppearance |= includeAll;
 
             final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder;
             builder.reset();
@@ -4757,6 +4761,10 @@
                 }
             }
 
+            if (includeTextAppearance) {
+                TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo(mTextView);
+                builder.setTextAppearanceInfo(textAppearanceInfo);
+            }
             imm.updateCursorAnchorInfo(mTextView, builder.build());
 
             // Drop the immediate flag if any.
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 5e74381..510a92d 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -116,9 +116,7 @@
  * <p class="note">ListView attempts to reuse view objects in order to improve performance and
  * avoid a lag in response to user scrolls.  To take advantage of this feature, check if the
  * {@code convertView} provided to {@code getView(...)} is null before creating or inflating a new
- * view object.  See
- * <a href="{@docRoot}training/improving-layouts/smooth-scrolling.html">
- * Making ListView Scrolling Smooth</a> for more ways to ensure a smooth user experience.</p>
+ * view object.</p>
  *
  * <p>To specify an action when a user clicks or taps on a single list item, see
  * <a href="{@docRoot}guide/topics/ui/declaring-layout.html#HandlingUserSelections">
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 36eaf49..57e0ce8 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -105,4 +105,7 @@
 
     /** @return An interface enabling the transition players to report its metrics. */
     ITransitionMetricsReporter getTransitionMetricsReporter();
+
+    /** @return The transaction queue token used by WM. */
+    IBinder getApplyToken();
 }
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 2a80d02..930aaa2 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -26,6 +26,7 @@
 import android.os.RemoteException;
 import android.util.Singleton;
 import android.view.RemoteAnimationAdapter;
+import android.view.SurfaceControl;
 
 /**
  * Base class for organizing specific types of windows like Tasks and DisplayAreas
@@ -184,6 +185,26 @@
         }
     }
 
+    /**
+     * Use WM's transaction-queue instead of Shell's independent one. This is necessary
+     * if WM and Shell need to coordinate transactions (eg. for shell transitions).
+     * @return true if successful, false otherwise.
+     * @hide
+     */
+    public boolean shareTransactionQueue() {
+        final IBinder wmApplyToken;
+        try {
+            wmApplyToken = getWindowOrganizerController().getApplyToken();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        if (wmApplyToken == null) {
+            return false;
+        }
+        SurfaceControl.Transaction.setDefaultApplyToken(wmApplyToken);
+        return true;
+    }
+
     static IWindowOrganizerController getWindowOrganizerController() {
         return IWindowOrganizerControllerSingleton.get();
     }
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index b155dd4..330ff8e 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -237,7 +237,9 @@
                 | InputConnection.CURSOR_UPDATE_MONITOR;
         final int knownFilterFlags = InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
                 | InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
-                | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS;
+                | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS
+                | InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS
+                | InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE;
 
         // It is possible that any other bit is used as a valid flag in a future release.
         // We should reject the entire request in such a case.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
similarity index 80%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
rename to core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
index 1550ab3..18bd6e5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.inputmethod;
 
-parcelable RemoteTransitionCompat;
+parcelable InputMethodSubtypeHandle;
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
new file mode 100644
index 0000000..780c637
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.security.InvalidParameterException;
+import java.util.Objects;
+
+/**
+ * A stable and serializable identifier for the pair of {@link InputMethodInfo#getId()} and
+ * {@link android.view.inputmethod.InputMethodSubtype}.
+ *
+ * <p>To save {@link InputMethodSubtypeHandle} to storage, call {@link #toStringHandle()} to get a
+ * {@link String} handle and just save it.  Once you load a {@link String} handle, you can obtain a
+ * {@link InputMethodSubtypeHandle} instance from {@link #of(String)}.</p>
+ *
+ * <p>For better readability, consider specifying {@link RawHandle} annotation to {@link String}
+ * object when it is a raw {@link String} handle.</p>
+ */
+public final class InputMethodSubtypeHandle implements Parcelable {
+    private static final String SUBTYPE_TAG = "subtype";
+    private static final char DATA_SEPARATOR = ':';
+
+    /**
+     * Can be used to annotate {@link String} object if it is raw handle format.
+     */
+    @Retention(SOURCE)
+    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.LOCAL_VARIABLE,
+            ElementType.PARAMETER})
+    public @interface RawHandle {
+    }
+
+    /**
+     * The main content of this {@link InputMethodSubtypeHandle}.  Is designed to be safe to be
+     * saved into storage.
+     */
+    @RawHandle
+    private final String mHandle;
+
+    /**
+     * Encode {@link InputMethodInfo} and {@link InputMethodSubtype#hashCode()} into
+     * {@link RawHandle}.
+     *
+     * @param imeId {@link InputMethodInfo#getId()} to be used.
+     * @param subtypeHashCode {@link InputMethodSubtype#hashCode()} to be used.
+     * @return The encoded {@link RawHandle} string.
+     */
+    @AnyThread
+    @RawHandle
+    @NonNull
+    private static String encodeHandle(@NonNull String imeId, int subtypeHashCode) {
+        return imeId + DATA_SEPARATOR + SUBTYPE_TAG + DATA_SEPARATOR + subtypeHashCode;
+    }
+
+    private InputMethodSubtypeHandle(@NonNull String handle) {
+        mHandle = handle;
+    }
+
+    /**
+     * Creates {@link InputMethodSubtypeHandle} from {@link InputMethodInfo} and
+     * {@link InputMethodSubtype}.
+     *
+     * @param imi {@link InputMethodInfo} to be used.
+     * @param subtype {@link InputMethodSubtype} to be used.
+     * @return A {@link InputMethodSubtypeHandle} object.
+     */
+    @AnyThread
+    @NonNull
+    public static InputMethodSubtypeHandle of(
+            @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+        final int subtypeHashCode =
+                subtype != null ? subtype.hashCode() : InputMethodSubtype.SUBTYPE_ID_NONE;
+        return new InputMethodSubtypeHandle(encodeHandle(imi.getId(), subtypeHashCode));
+    }
+
+    /**
+     * Creates {@link InputMethodSubtypeHandle} from a {@link RawHandle} {@link String}, which can
+     * be obtained by {@link #toStringHandle()}.
+     *
+     * @param stringHandle {@link RawHandle} {@link String} to be parsed.
+     * @return A {@link InputMethodSubtypeHandle} object.
+     * @throws NullPointerException when {@code stringHandle} is {@code null}
+     * @throws InvalidParameterException when {@code stringHandle} is not a valid {@link RawHandle}.
+     */
+    @AnyThread
+    @NonNull
+    public static InputMethodSubtypeHandle of(@RawHandle @NonNull String stringHandle) {
+        final SimpleStringSplitter splitter = new SimpleStringSplitter(DATA_SEPARATOR);
+        splitter.setString(Objects.requireNonNull(stringHandle));
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String imeId = splitter.next();
+        final ComponentName componentName = ComponentName.unflattenFromString(imeId);
+        if (componentName == null) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        // TODO: Consolidate IME ID validation logic into one place.
+        if (!Objects.equals(componentName.flattenToShortString(), imeId)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String source = splitter.next();
+        if (!Objects.equals(source, SUBTYPE_TAG)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String hashCodeStr = splitter.next();
+        if (splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final int subtypeHashCode;
+        try {
+            subtypeHashCode = Integer.parseInt(hashCodeStr);
+        } catch (NumberFormatException ignore) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+
+        // Redundant expressions (e.g. "0001" instead of "1") are not allowed.
+        if (!Objects.equals(encodeHandle(imeId, subtypeHashCode), stringHandle)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+
+        return new InputMethodSubtypeHandle(stringHandle);
+    }
+
+    /**
+     * @return {@link ComponentName} of the input method.
+     * @see InputMethodInfo#getComponent()
+     */
+    @AnyThread
+    @NonNull
+    public ComponentName getComponentName() {
+        return ComponentName.unflattenFromString(getImeId());
+    }
+
+    /**
+     * @return IME ID.
+     * @see InputMethodInfo#getId()
+     */
+    @AnyThread
+    @NonNull
+    public String getImeId() {
+        return mHandle.substring(0, mHandle.indexOf(DATA_SEPARATOR));
+    }
+
+    /**
+     * @return {@link RawHandle} {@link String} data that should be stable and persistable.
+     * @see #of(String)
+     */
+    @RawHandle
+    @AnyThread
+    @NonNull
+    public String toStringHandle() {
+        return mHandle;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof InputMethodSubtypeHandle)) {
+            return false;
+        }
+        final InputMethodSubtypeHandle that = (InputMethodSubtypeHandle) obj;
+        return Objects.equals(mHandle, that.mHandle);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mHandle);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @NonNull
+    @Override
+    public String toString() {
+        return "InputMethodSubtypeHandle{mHandle=" + mHandle + "}";
+    }
+
+    /**
+     * {@link Creator} for parcelable.
+     */
+    public static final Creator<InputMethodSubtypeHandle> CREATOR = new Creator<>() {
+        @Override
+        public InputMethodSubtypeHandle createFromParcel(Parcel in) {
+            return of(in.readString8());
+        }
+
+        @Override
+        public InputMethodSubtypeHandle[] newArray(int size) {
+            return new InputMethodSubtypeHandle[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(toStringHandle());
+    }
+}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 98d81c9..6870d09 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -51,6 +51,15 @@
     // ------ ro.fw.* ------------ //
     public static final boolean FW_SYSTEM_USER_SPLIT =
             SystemProperties.getBoolean("ro.fw.system_user_split", false);
+    /**
+     * Indicates whether the device should run in headless system user mode,
+     *   in which user 0 only runs the system, not a real user.
+     * <p>WARNING about changing this value during an non-wiping update (OTA):
+     *   <li>If this value is modified via an update, the change will have no effect, since an
+     *       already-existing system user cannot change its mode.
+     *   <li>Changing this value during an OTA from a pre-R device is not permitted; attempting to
+     *       do so will corrupt the system user.
+     */
     public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER =
             SystemProperties.getBoolean("ro.fw.mu.headless_system_user", false);
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 1d4b246..edbdc86 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -23,13 +23,12 @@
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.media.INearbyMediaDevicesProvider;
 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;
@@ -175,9 +174,9 @@
     void setBiometicContextListener(in IBiometricContextListener listener);
 
     /**
-     * Sets an instance of IUdfpsHbmListener for UdfpsController.
+     * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController.
      */
-    void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+    void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
 
     /**
      * Notifies System UI that the display is ready to show system decorations.
@@ -201,13 +200,13 @@
      *                         stacks.
      * @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME.
      * @param behavior the behavior of the focused window.
-     * @param requestedVisibilities the collection of the requested visibilities of system insets.
+     * @param requestedVisibleTypes the collection of insets types requested visible.
      * @param packageName the package name of the focused app.
      * @param letterboxDetails a set of letterbox details of apps visible on the screen.
      */
     void onSystemBarAttributesChanged(int displayId, int appearance,
             in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            int behavior, in InsetsVisibilities requestedVisibilities, String packageName,
+            int behavior, int requestedVisibleTypes, String packageName,
             in LetterboxDetails[] letterboxDetails);
 
     /**
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ef8f2db..d190681 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -23,7 +23,7 @@
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.net.Uri;
@@ -136,9 +136,9 @@
     void setBiometicContextListener(in IBiometricContextListener listener);
 
     /**
-     * Sets an instance of IUdfpsHbmListener for UdfpsController.
+     * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController.
      */
-    void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+    void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
 
     /**
      * Show a warning that the device is about to go to sleep due to user inactivity.
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 8b898f0..54221ce 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -21,7 +21,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
 
 import com.android.internal.view.AppearanceRegion;
 
@@ -40,7 +39,7 @@
     public final IBinder mImeToken;
     public final boolean mNavbarColorManagedByIme;
     public final int mBehavior;
-    public final InsetsVisibilities mRequestedVisibilities;
+    public final int mRequestedVisibleTypes;
     public final String mPackageName;
     public final int[] mTransientBarTypes;
     public final LetterboxDetails[] mLetterboxDetails;
@@ -48,7 +47,7 @@
     public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
             int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
             int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
-            boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities,
+            boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
             String packageName, @NonNull int[] transientBarTypes,
             LetterboxDetails[] letterboxDetails) {
         mIcons = new ArrayMap<>(icons);
@@ -62,7 +61,7 @@
         mImeToken = imeToken;
         mNavbarColorManagedByIme = navbarColorManagedByIme;
         mBehavior = behavior;
-        mRequestedVisibilities = requestedVisibilities;
+        mRequestedVisibleTypes = requestedVisibleTypes;
         mPackageName = packageName;
         mTransientBarTypes = transientBarTypes;
         mLetterboxDetails = letterboxDetails;
@@ -86,7 +85,7 @@
         dest.writeStrongBinder(mImeToken);
         dest.writeBoolean(mNavbarColorManagedByIme);
         dest.writeInt(mBehavior);
-        dest.writeTypedObject(mRequestedVisibilities, 0);
+        dest.writeInt(mRequestedVisibleTypes);
         dest.writeString(mPackageName);
         dest.writeIntArray(mTransientBarTypes);
         dest.writeParcelableArray(mLetterboxDetails, flags);
@@ -112,8 +111,7 @@
                     final IBinder imeToken = source.readStrongBinder();
                     final boolean navbarColorManagedByIme = source.readBoolean();
                     final int behavior = source.readInt();
-                    final InsetsVisibilities requestedVisibilities =
-                            source.readTypedObject(InsetsVisibilities.CREATOR);
+                    final int requestedVisibleTypes = source.readInt();
                     final String packageName = source.readString();
                     final int[] transientBarTypes = source.createIntArray();
                     final LetterboxDetails[] letterboxDetails =
@@ -121,7 +119,7 @@
                     return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
                             appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
                             disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
-                            requestedVisibilities, packageName, transientBarTypes,
+                            requestedVisibleTypes, packageName, transientBarTypes,
                             letterboxDetails);
                 }
 
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index f7bb16e..40c6a05 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -81,8 +81,7 @@
     @EnforcePermission("WRITE_SECURE_SETTINGS")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    void showInputMethodPickerFromSystem(in IInputMethodClient client,
-            int auxiliarySubtypeMode, int displayId);
+    void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
 
     @EnforcePermission("TEST_INPUT_METHOD")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 2cd9f89..6762e45 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -22,6 +22,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <linux/fs.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <stdlib.h>
 #include <string.h>
@@ -250,6 +251,16 @@
         return INSTALL_FAILED_CONTAINER_ERROR;
     }
 
+    // If a filesystem like f2fs supports per-file compression, set the compression bit before data
+    // writes
+    unsigned int flags;
+    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
+        ALOGE("Failed to call FS_IOC_GETFLAGS on %s: %s\n", localTmpFileName, strerror(errno));
+    } else if ((flags & FS_COMPR_FL) == 0) {
+        flags |= FS_COMPR_FL;
+        ioctl(fd, FS_IOC_SETFLAGS, &flags);
+    }
+
     if (!zipFile->uncompressEntry(zipEntry, fd)) {
         ALOGE("Failed uncompressing %s to %s\n", fileName, localTmpFileName);
         close(fd);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 16e0a59..f0fbff1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7211,6 +7211,10 @@
             </intent-filter>
         </service>
 
+        <service android:name="com.android.server.art.BackgroundDexOptJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <provider
             android:name="com.android.server.textclassifier.IconsContentProvider"
             android:authorities="com.android.textclassifier.icons"
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 88bf18c..eaadc20 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -54,6 +54,12 @@
         com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity
     </string>
 
+    <!-- Component name of the activity used to inform a user about a sensor privacy update from
+     SW/HW privacy switches. -->
+    <string name="config_sensorStateChangedActivity" translatable="false">
+        com.android.systemui/com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity
+    </string>
+
     <!-- Component name of the activity that shows the request for access to a usb device. -->
     <string name="config_usbPermissionActivity" translatable="false">
         com.android.systemui/com.android.systemui.usb.tv.TvUsbPermissionActivity
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 23dd1b4..7d20335 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2456,8 +2456,10 @@
          assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
     <bool name="config_dismissDreamOnActivityStart">false</bool>
 
-    <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. -->
-    <string name="config_loggable_dream_prefix" translatable="false"></string>
+    <!-- The prefixes of dream component names that are loggable.
+         Matched against ComponentName#flattenToString() for dream components.
+         If empty, logs "other" for all. -->
+    <string-array name="config_loggable_dream_prefixes"></string-array>
 
     <!-- ComponentName of a dream to show whenever the system would otherwise have
          gone to sleep.  When the PowerManager is asked to go to sleep, it will instead
@@ -2942,6 +2944,9 @@
     <string name="config_usbResolverActivity" translatable="false"
             >com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
 
+    <!-- Component name of the activity used to inform a user about a sensor privacy state chage. -->
+    <string name="config_sensorStateChangedActivity" translatable="false"></string>
+
     <!-- Component name of the activity used to inform a user about a sensory being blocked because
      of privacy settings. -->
     <string name="config_sensorUseStartedActivity" translatable="false"
@@ -4425,13 +4430,13 @@
     <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
 
     <!-- Corner radius of system dialogs -->
-    <dimen name="config_dialogCornerRadius">2dp</dimen>
+    <dimen name="config_dialogCornerRadius">28dp</dimen>
     <!-- Corner radius of system buttons -->
-    <dimen name="config_buttonCornerRadius">@dimen/control_corner_material</dimen>
+    <dimen name="config_buttonCornerRadius">4dp</dimen>
     <!-- Corner radius for bottom sheet system dialogs -->
-    <dimen name="config_bottomDialogCornerRadius">@dimen/config_dialogCornerRadius</dimen>
+    <dimen name="config_bottomDialogCornerRadius">16dp</dimen>
     <!-- Corner radius of system progress bars -->
-    <dimen name="config_progressBarCornerRadius">@dimen/progress_bar_corner_material</dimen>
+    <dimen name="config_progressBarCornerRadius">1000dp</dimen>
     <!-- Controls whether system buttons use all caps for text -->
     <bool name="config_buttonTextAllCaps">true</bool>
     <!-- Name of the font family used for system surfaces where the font should use medium weight -->
@@ -4945,6 +4950,11 @@
     <!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
     <bool name="config_faceAuthDismissesKeyguard">true</bool>
 
+    <!-- Default value for whether a SFPS device is required to be
+        {@link KeyguardUpdateMonitor#isDeviceInteractive()} for fingerprint auth
+        to unlock the device. -->
+    <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+
     <!-- The component name for the default profile supervisor, which can be set as a profile owner
     even after user setup is complete. The defined component should be used for supervision purposes
     only. The component must be part of a system app. -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 1997261..9dbb6a0 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -979,9 +979,9 @@
     <dimen name="controls_thumbnail_image_max_width">280dp</dimen>
 
     <!-- System-provided radius for the background view of app widgets. The resolved value of this resource may change at runtime. -->
-    <dimen name="system_app_widget_background_radius">16dp</dimen>
+    <dimen name="system_app_widget_background_radius">28dp</dimen>
     <!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. -->
-    <dimen name="system_app_widget_inner_radius">8dp</dimen>
+    <dimen name="system_app_widget_inner_radius">20dp</dimen>
     <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. @removed -->
     <dimen name="__removed_system_app_widget_internal_padding">16dp</dimen>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 476d36d..2a17199 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -374,6 +374,7 @@
   <java-symbol type="string" name="config_usbResolverActivity" />
   <java-symbol type="string" name="config_sensorUseStartedActivity" />
   <java-symbol type="string" name="config_sensorUseStartedActivity_hwToggle" />
+  <java-symbol type="string" name="config_sensorStateChangedActivity" />
   <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
@@ -2246,7 +2247,7 @@
   <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
   <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
   <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
-  <java-symbol type="string" name="config_loggable_dream_prefix" />
+  <java-symbol type="array" name="config_loggable_dream_prefixes" />
   <java-symbol type="string" name="config_dozeComponent" />
   <java-symbol type="string" name="enable_explore_by_touch_warning_title" />
   <java-symbol type="string" name="enable_explore_by_touch_warning_message" />
@@ -2725,6 +2726,7 @@
   <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
   <java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
   <java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
+  <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
 
   <!-- Face config -->
   <java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
diff --git a/core/tests/coretests/src/android/app/activity/OWNERS b/core/tests/coretests/src/android/app/activity/OWNERS
index 0862c05..7e24aef 100644
--- a/core/tests/coretests/src/android/app/activity/OWNERS
+++ b/core/tests/coretests/src/android/app/activity/OWNERS
@@ -1 +1,2 @@
 include /services/core/java/com/android/server/wm/OWNERS
+include /services/core/java/com/android/server/am/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
new file mode 100644
index 0000000..f111bf6f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidParameterException;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InputMethodSubtypeHandleTest {
+
+    @Test
+    public void testCreateFromRawHandle() {
+        {
+            final InputMethodSubtypeHandle handle =
+                    InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(""));
+
+        // The IME ID must use ComponentName#flattenToShortString(), not #flattenToString().
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/com.android.test.Ime1:subtype:1"));
+
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:0001"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1!"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1:"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1:2"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:a"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:0x01"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:Subtype:a"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "ime1:subtype:1"));
+    }
+
+    @Test
+    public void testCreateFromInputMethodInfo() {
+        final InputMethodInfo imi = new InputMethodInfo(
+                "com.android.test", "com.android.test.Ime1", "TestIME", null);
+        {
+            final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, null);
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:0", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        final InputMethodSubtype subtype =
+                new InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(1).build();
+        {
+            final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, subtype);
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, null));
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, subtype));
+    }
+
+    @Test
+    public void testEquality() {
+        assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"));
+        assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode(),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode());
+
+        assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:2"));
+        assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime2:subtype:1"));
+    }
+
+    @Test
+    public void testParcelablility() {
+        final InputMethodSubtypeHandle original =
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+        final InputMethodSubtypeHandle cloned = cloneHandle(original);
+        assertEquals(original, cloned);
+        assertEquals(original.hashCode(), cloned.hashCode());
+        assertEquals(original.getComponentName(), cloned.getComponentName());
+        assertEquals(original.getImeId(), cloned.getImeId());
+        assertEquals(original.toStringHandle(), cloned.toStringHandle());
+    }
+
+    @Test
+    public void testNoUnnecessaryStringInstantiationInToStringHandle() {
+        final String validHandleStr = "com.android.test/.Ime1:subtype:1";
+        // Verify that toStringHandle() returns the same String object if the input is valid for
+        // an efficient memory usage.
+        assertSame(validHandleStr, InputMethodSubtypeHandle.of(validHandleStr).toStringHandle());
+    }
+
+    @NonNull
+    private static InputMethodSubtypeHandle cloneHandle(
+            @NonNull InputMethodSubtypeHandle original) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return InputMethodSubtypeHandle.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index c53fb23..048c48b 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -25,7 +25,7 @@
 import android.os.Parcel;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -65,7 +65,7 @@
                 new Binder() /* imeToken */,
                 true /* navbarColorManagedByIme */,
                 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                new InsetsVisibilities() /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 "test" /* packageName */,
                 new int[0] /* transientBarTypes */,
                 new LetterboxDetails[] {letterboxDetails});
@@ -87,7 +87,7 @@
         assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
         assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
         assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
-        assertThat(copy.mRequestedVisibilities).isEqualTo(original.mRequestedVisibilities);
+        assertThat(copy.mRequestedVisibleTypes).isEqualTo(original.mRequestedVisibleTypes);
         assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
         assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
         assertThat(copy.mLetterboxDetails).isEqualTo(original.mLetterboxDetails);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1d513e4..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1873,6 +1873,11 @@
         @Override
         public void onActivityPreCreated(@NonNull Activity activity,
                 @Nullable Bundle savedInstanceState) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 final IBinder activityToken = activity.getActivityToken();
                 final IBinder initialTaskFragmentToken =
@@ -1904,6 +1909,11 @@
         @Override
         public void onActivityPostCreated(@NonNull Activity activity,
                 @Nullable Bundle savedInstanceState) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             // Calling after Activity#onCreate is complete to allow the app launch something
             // first. In case of a configured placeholder activity we want to make sure
             // that we don't launch it if an activity itself already requested something to be
@@ -1921,6 +1931,11 @@
 
         @Override
         public void onActivityConfigurationChanged(@NonNull Activity activity) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 final TransactionRecord transactionRecord = mTransactionManager
                         .startNewTransaction();
@@ -1934,6 +1949,11 @@
 
         @Override
         public void onActivityPostDestroyed(@NonNull Activity activity) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 SplitController.this.onActivityDestroyed(activity);
             }
@@ -1969,7 +1989,11 @@
             if (who instanceof Activity) {
                 // We will check if the new activity should be split with the activity that launched
                 // it.
-                launchingActivity = (Activity) who;
+                final Activity activity = (Activity) who;
+                // For Activity that is child of another Activity (ActivityGroup), treat the parent
+                // Activity as the launching one because it's window will just be a child of the
+                // parent Activity window.
+                launchingActivity = activity.isChild() ? activity.getParent() : activity;
                 if (isInPictureInPicture(launchingActivity)) {
                     // We don't embed activity when it is in PIP.
                     return super.onStartActivity(who, intent, options);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 7960323..362f1fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -227,7 +227,7 @@
         final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
                 secondaryActivity);
         TaskFragmentContainer containerToAvoid = primaryContainer;
-        if (curSecondaryContainer != null
+        if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
                 && (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
             // Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
             // the primary TaskFragment.
diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
new file mode 100644
index 0000000..8779cc0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
@@ -0,0 +1,31 @@
+<?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="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="6.0"
+           android:translateY="6.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
new file mode 100644
index 0000000..ea0fbb0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
@@ -0,0 +1,31 @@
+<?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="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="6.0"
+           android:translateY="6.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
new file mode 100644
index 0000000..c55cbe2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
@@ -0,0 +1,31 @@
+<?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="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="6.0"
+           android:translateY="6.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
new file mode 100644
index 0000000..447df43
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
@@ -0,0 +1,31 @@
+<?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="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="6.0"
+           android:translateY="6.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
new file mode 100644
index 0000000..c334a54
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:translateX="6.0"
+           android:translateY="8.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/>    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
new file mode 100644
index 0000000..e307f00
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/handle_menu_background.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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="210.0dp"
+        android:height="64.0dp"
+        android:tint="@color/decor_button_light_color"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="8.0"
+           android:translateY="8.0" >
+        <path
+            android:fillColor="@android:color/white"
+            android:pathData="M18.3334 14L13.3334 14L13.3334 2L18.3334 2L18.3334 14ZM20.3334 14L20.3334 2C20.3334 0.9 19.4334 -3.93402e-08 18.3334 -8.74228e-08L13.3334 -3.0598e-07C12.2334 -3.54062e-07 11.3334 0.9 11.3334 2L11.3334 14C11.3334 15.1 12.2334 16 13.3334 16L18.3334 16C19.4334 16 20.3334 15.1 20.3334 14ZM7.33337 14L2.33337 14L2.33337 2L7.33337 2L7.33337 14ZM9.33337 14L9.33337 2C9.33337 0.899999 8.43337 -5.20166e-07 7.33337 -5.68248e-07L2.33337 -7.86805e-07C1.23337 -8.34888e-07 0.333374 0.899999 0.333374 2L0.333373 14C0.333373 15.1 1.23337 16 2.33337 16L7.33337 16C8.43337 16 9.33337 15.1 9.33337 14Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
new file mode 100644
index 0000000..d9a140b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
@@ -0,0 +1,49 @@
+<?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.wm.shell.windowdecor.WindowDecorLinearLayout
+xmlns:android="http://schemas.android.com/apk/res/android"
+android:id="@+id/handle_menu"
+android:layout_width="wrap_content"
+android:layout_height="wrap_content"
+android:gravity="center_horizontal"
+android:background="@drawable/decor_caption_title">
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/fullscreen_button"
+        android:contentDescription="@string/fullscreen_text"
+        android:background="@drawable/caption_fullscreen_button"/>
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/split_screen_button"
+        android:contentDescription="@string/split_screen_text"
+        android:background="@drawable/caption_split_screen_button"/>
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/floating_button"
+        android:contentDescription="@string/float_button_text"
+        android:background="@drawable/caption_floating_button"/>
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/desktop_button"
+        android:contentDescription="@string/desktop_text"
+        android:background="@drawable/caption_desktop_button"/>
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/more_button"
+        android:contentDescription="@string/more_button_text"
+        android:background="@drawable/caption_more_button"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index 38cd570..51e634c 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -19,14 +19,10 @@
     android:id="@+id/caption"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:gravity="center_horizontal"
     android:background="@drawable/decor_caption_title">
     <Button
+        style="@style/CaptionButtonStyle"
         android:id="@+id/back_button"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:layout_margin="5dp"
-        android:padding="4dp"
         android:contentDescription="@string/back_button_text"
         android:background="@drawable/decor_back_button_dark"
     />
@@ -39,11 +35,8 @@
         android:contentDescription="@string/handle_text"
         android:background="@drawable/decor_handle_dark"/>
     <Button
+        style="@style/CaptionButtonStyle"
         android:id="@+id/close_window"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:layout_margin="5dp"
-        android:padding="4dp"
         android:contentDescription="@string/close_button_text"
         android:background="@drawable/decor_close_button_dark"/>
 </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4807f08..097a567 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -202,4 +202,14 @@
     <string name="back_button_text">Back</string>
     <!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
     <string name="handle_text">Handle</string>
+    <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
+    <string name="fullscreen_text">Fullscreen</string>
+    <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
+    <string name="desktop_text">Desktop Mode</string>
+    <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
+    <string name="split_screen_text">Split Screen</string>
+    <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
+    <string name="more_button_text">More</string>
+    <!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] -->
+    <string name="float_button_text">Float</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 19f7c3e..a859721 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -30,6 +30,13 @@
         <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
     </style>
 
+    <style name="CaptionButtonStyle">
+        <item name="android:layout_width">32dp</item>
+        <item name="android:layout_height">32dp</item>
+        <item name="android:layout_margin">5dp</item>
+        <item name="android:padding">4dp</item>
+    </style>
+
     <style name="DockedDividerBackground">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">@dimen/split_divider_bar_width</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index dc5b9a1e..64220c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -38,6 +38,7 @@
 import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowFocusObserver;
 import android.view.InputDevice;
@@ -187,6 +188,31 @@
         }
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
+
+        initBackAnimationRunners();
+    }
+
+    private void initBackAnimationRunners() {
+        final IOnBackInvokedCallback dummyCallback = new IOnBackInvokedCallback.Default();
+        final IRemoteAnimationRunner dummyRunner = new IRemoteAnimationRunner.Default() {
+            @Override
+            public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                    IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+                // Animation missing. Simply finish animation.
+                finishedCallback.onAnimationFinished();
+            }
+        };
+
+        final BackAnimationRunner dummyBackRunner =
+                new BackAnimationRunner(dummyCallback, dummyRunner);
+        final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
+                new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
+        // TODO (238474994): register cross activity animation when it's completed.
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, dummyBackRunner);
+        // TODO (236760237): register dialog close animation when it's completed.
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_DIALOG_CLOSE, dummyBackRunner);
     }
 
     private void setupAnimationDeveloperSettingsObserver(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
new file mode 100644
index 0000000..2074b6a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Controls the animation of swiping back and returning to another task.
+ *
+ * This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows.
+ * Once the gesture is committed, the second part remains the closing window in place.
+ * The entering window plays the rest of app opening transition to enter full screen.
+ *
+ * This animation is used only for apps that enable back dispatching via
+ * {@link android.window.OnBackInvokedDispatcher}. The controller registers
+ * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
+ * navigation to launcher starts.
+ */
+@ShellMainThread
+class CrossTaskBackAnimation {
+    private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+
+    /**
+     * Minimum scale of the entering window.
+     */
+    private static final float ENTERING_MIN_WINDOW_SCALE = 0.85f;
+
+    /**
+     * Minimum scale of the closing window.
+     */
+    private static final float CLOSING_MIN_WINDOW_SCALE = 0.75f;
+
+    /**
+     * Minimum color scale of the closing window.
+     */
+    private static final float CLOSING_MIN_WINDOW_COLOR_SCALE = 0.1f;
+
+    /**
+     * The margin between the entering window and the closing window
+     */
+    private static final int WINDOW_MARGIN = 35;
+
+    /** Max window translation in the Y axis. */
+    private static final int WINDOW_MAX_DELTA_Y = 160;
+
+    private final Rect mStartTaskRect = new Rect();
+    private final float mCornerRadius;
+
+    // The closing window properties.
+    private final RectF mClosingCurrentRect = new RectF();
+
+    // The entering window properties.
+    private final Rect mEnteringStartRect = new Rect();
+    private final RectF mEnteringCurrentRect = new RectF();
+
+    private final PointF mInitialTouchPos = new PointF();
+    private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+
+    private final Matrix mTransformMatrix = new Matrix();
+
+    private final float[] mTmpFloat9 = new float[9];
+    private final float[] mTmpTranslate = {0, 0, 0};
+
+    private RemoteAnimationTarget mEnteringTarget;
+    private RemoteAnimationTarget mClosingTarget;
+    private SurfaceControl mBackgroundSurface;
+    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private boolean mBackInProgress = false;
+
+    private boolean mIsRightEdge;
+    private float mProgress = 0;
+    private PointF mTouchPos = new PointF();
+    private IRemoteAnimationFinishedCallback mFinishCallback;
+
+    private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+
+    final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
+        @Override
+        public void onBackStarted(BackEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossTaskBackAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    };
+
+    final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
+        @Override
+        public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+    };
+
+    CrossTaskBackAnimation(Context context) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+    }
+
+    private float getInterpolatedProgress(float backProgress) {
+        return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
+    }
+
+    private void startBackAnimation() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+            return;
+        }
+
+        // Offset start rectangle to align task bounds.
+        mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+        mStartTaskRect.offsetTo(0, 0);
+
+        // Draw background.
+        mBackgroundSurface = new SurfaceControl.Builder()
+                .setName("Background of Back Navigation")
+                .setColorLayer()
+                .setHidden(false)
+                .build();
+        mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
+                .setLayer(mBackgroundSurface, -1);
+        mTransaction.apply();
+    }
+
+    private void updateGestureBackProgress(float progress, BackEvent event) {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            return;
+        }
+
+        float touchX = event.getTouchX();
+        float touchY = event.getTouchY();
+        float dX = Math.abs(touchX - mInitialTouchPos.x);
+
+        // The 'follow width' is the width of the window if it completely matches
+        // the gesture displacement.
+        final int width = mStartTaskRect.width();
+        final int height = mStartTaskRect.height();
+
+        // The 'progress width' is the width of the window if it strictly linearly interpolates
+        // to minimum scale base on progress.
+        float enteringScale = mapRange(progress, 1, ENTERING_MIN_WINDOW_SCALE);
+        float closingScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_SCALE);
+        float closingColorScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_COLOR_SCALE);
+
+        // The final width is derived from interpolating between the follow with and progress width
+        // using gesture progress.
+        float enteringWidth = enteringScale * width;
+        float closingWidth = closingScale * width;
+        float enteringHeight = (float) height / width * enteringWidth;
+        float closingHeight = (float) height / width * closingWidth;
+
+        float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+        // Base the window movement in the Y axis on the touch movement in the Y axis.
+        float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+        // Move the window along the Y axis.
+        float closingTop = (height - closingHeight) * 0.5f + deltaY;
+        float enteringTop = (height - enteringHeight) * 0.5f + deltaY;
+        // Move the window along the X axis.
+        float right = width - (progress * WINDOW_MARGIN);
+        float left = right - closingWidth;
+
+        mClosingCurrentRect.set(left, closingTop, right, closingTop + closingHeight);
+        mEnteringCurrentRect.set(left - enteringWidth - WINDOW_MARGIN, enteringTop,
+                left - WINDOW_MARGIN, enteringTop + enteringHeight);
+
+        applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
+        applyColorTransform(mClosingTarget.leash, closingColorScale);
+        applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+        mTransaction.apply();
+    }
+
+    private void updatePostCommitClosingAnimation(float progress) {
+        mTransaction.setLayer(mClosingTarget.leash, 0);
+        float alpha = mapRange(progress, 1, 0);
+        mTransaction.setAlpha(mClosingTarget.leash, alpha);
+    }
+
+    private void updatePostCommitEnteringAnimation(float progress) {
+        float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+        float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+        float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+        float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+
+        mEnteringCurrentRect.set(left, top, left + width, top + height);
+        applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+    }
+
+    /** Transform the target window to match the target rect. */
+    private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
+        if (leash == null) {
+            return;
+        }
+
+        final float scale = targetRect.width() / mStartTaskRect.width();
+        mTransformMatrix.reset();
+        mTransformMatrix.setScale(scale, scale);
+        mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+        mTransaction.setMatrix(leash, mTransformMatrix, mTmpFloat9)
+                .setWindowCrop(leash, mStartTaskRect)
+                .setCornerRadius(leash, cornerRadius);
+    }
+
+    private void applyColorTransform(SurfaceControl leash, float colorScale) {
+        if (leash == null) {
+            return;
+        }
+        computeScaleTransformMatrix(colorScale, mTmpFloat9);
+        mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
+    }
+
+    static void computeScaleTransformMatrix(float scale, float[] matrix) {
+        matrix[0] = scale;
+        matrix[1] = 0;
+        matrix[2] = 0;
+        matrix[3] = 0;
+        matrix[4] = scale;
+        matrix[5] = 0;
+        matrix[6] = 0;
+        matrix[7] = 0;
+        matrix[8] = scale;
+    }
+
+    private void finishAnimation() {
+        if (mEnteringTarget != null) {
+            mEnteringTarget.leash.release();
+            mEnteringTarget = null;
+        }
+        if (mClosingTarget != null) {
+            mClosingTarget.leash.release();
+            mClosingTarget = null;
+        }
+
+        if (mBackgroundSurface != null) {
+            mBackgroundSurface.release();
+            mBackgroundSurface = null;
+        }
+
+        mBackInProgress = false;
+        mTransformMatrix.reset();
+        mClosingCurrentRect.setEmpty();
+        mInitialTouchPos.set(0, 0);
+
+        if (mFinishCallback != null) {
+            try {
+                mFinishCallback.onAnimationFinished();
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            mFinishCallback = null;
+        }
+    }
+
+    private void onGestureProgress(@NonNull BackEvent backEvent) {
+        if (!mBackInProgress) {
+            mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+            mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
+            mBackInProgress = true;
+        }
+        mProgress = backEvent.getProgress();
+        mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+        updateGestureBackProgress(getInterpolatedProgress(mProgress), backEvent);
+    }
+
+    private void onGestureCommitted() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            finishAnimation();
+            return;
+        }
+
+        // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+        // coordinate of the gesture driven phase.
+        mEnteringCurrentRect.round(mEnteringStartRect);
+
+        ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(300);
+        valueAnimator.setInterpolator(mInterpolator);
+        valueAnimator.addUpdateListener(animation -> {
+            float progress = animation.getAnimatedFraction();
+            updatePostCommitEnteringAnimation(progress);
+            updatePostCommitClosingAnimation(progress);
+            mTransaction.apply();
+        });
+
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishAnimation();
+            }
+        });
+        valueAnimator.start();
+    }
+
+    private static float mapRange(float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
new file mode 100644
index 0000000..7237d2b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-modules splitscreen owner
+chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 5b7d141..295a2e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -86,9 +86,9 @@
     private static final int FLING_ENTER_DURATION = 350;
     private static final int FLING_EXIT_DURATION = 350;
 
-    private final int mDividerWindowWidth;
-    private final int mDividerInsets;
-    private final int mDividerSize;
+    private int mDividerWindowWidth;
+    private int mDividerInsets;
+    private int mDividerSize;
 
     private final Rect mTempRect = new Rect();
     private final Rect mRootBounds = new Rect();
@@ -131,6 +131,7 @@
         mContext = context.createConfigurationContext(configuration);
         mOrientation = configuration.orientation;
         mRotation = configuration.windowConfiguration.getRotation();
+        mDensity = configuration.densityDpi;
         mSplitLayoutHandler = splitLayoutHandler;
         mDisplayImeController = displayImeController;
         mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
@@ -139,24 +140,22 @@
         mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
         mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
 
-        final Resources resources = context.getResources();
-        mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
-        mDividerInsets = getDividerInsets(resources, context.getDisplay());
-        mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
+        updateDividerConfig(mContext);
 
         mRootBounds.set(configuration.windowConfiguration.getBounds());
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
         resetDividerPosition();
 
-        mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+        mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
 
         updateInvisibleRect();
     }
 
-    private int getDividerInsets(Resources resources, Display display) {
+    private void updateDividerConfig(Context context) {
+        final Resources resources = context.getResources();
+        final Display display = context.getDisplay();
         final int dividerInset = resources.getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_insets);
-
         int radius = 0;
         RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
@@ -167,7 +166,9 @@
         corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
 
-        return Math.max(dividerInset, radius);
+        mDividerInsets = Math.max(dividerInset, radius);
+        mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+        mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
     }
 
     /** Gets bounds of the primary split with screen based coordinate. */
@@ -309,6 +310,7 @@
         mRotation = rotation;
         mDensity = density;
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+        updateDividerConfig(mContext);
         initDividerPosition(mTempRect);
         updateInvisibleRect();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index f1670cd..4459f57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,6 +27,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewTransitions;
@@ -271,11 +272,12 @@
             TaskStackListenerImpl taskStackListener,
             UiEventLogger uiEventLogger,
             InteractionJankMonitor jankMonitor,
+            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler) {
         return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
                 windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
-                uiEventLogger, mainExecutor, mainHandler);
+                uiEventLogger, rootDisplayAreaOrganizer, mainExecutor, mainHandler);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
index b310ee2..b5ed509 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -34,6 +34,7 @@
 import android.os.Binder;
 import android.util.Slog;
 import android.view.ContextThemeWrapper;
+import android.view.Display;
 import android.view.IWindow;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
@@ -47,6 +48,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
 
 import java.io.PrintWriter;
@@ -67,11 +69,14 @@
     private SurfaceControl mLeash;
     private View mBackgroundView;
     private @OneHandedState.State int mCurrentState;
+    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
 
-    public BackgroundWindowManager(Context context) {
+    public BackgroundWindowManager(Context context,
+            RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
         super(context.getResources().getConfiguration(), null /* rootSurface */,
                 null /* hostInputToken */);
         mContext = context;
+        mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
         mTransactionFactory = SurfaceControl.Transaction::new;
     }
 
@@ -112,6 +117,7 @@
                 .setOpaque(true)
                 .setName(TAG)
                 .setCallsite("BackgroundWindowManager#attachToParentSurface");
+        mRootDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, builder);
         mLeash = builder.build();
         b.setParent(mLeash);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 679d4ca..ad135d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -47,6 +47,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.wm.shell.R;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
@@ -204,12 +205,14 @@
             DisplayController displayController, DisplayLayout displayLayout,
             TaskStackListenerImpl taskStackListener,
             InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
+            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
             ShellExecutor mainExecutor, Handler mainHandler) {
         OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
         OneHandedState oneHandedState = new OneHandedState();
-        BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
+        BackgroundWindowManager backgroundWindowManager =
+                new BackgroundWindowManager(context, rootDisplayAreaOrganizer);
         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
                 settingsUtil, windowManager, backgroundWindowManager);
         OneHandedAnimationController animationController =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 0006244..f707363 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.RemoteAction;
@@ -73,6 +74,11 @@
     void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction);
 
     /**
+     * Wait until the next frame to run the given Runnable.
+     */
+    void runWithNextFrame(@NonNull Runnable runnable);
+
+    /**
      * Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
      * need to synchronize the movements on the same frame as PiP.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..2d7c5ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -179,8 +179,10 @@
                 // This is necessary in case there was a resize animation ongoing when exit PIP
                 // started, in which case the first resize will be skipped to let the exit
                 // operation handle the final resize out of PIP mode. See b/185306679.
-                finishResize(tx, destinationBounds, direction, animationType);
-                sendOnPipTransitionFinished(direction);
+                finishResizeDelayedIfNeeded(() -> {
+                    finishResize(tx, destinationBounds, direction, animationType);
+                    sendOnPipTransitionFinished(direction);
+                });
             }
         }
 
@@ -196,6 +198,34 @@
         }
     };
 
+    /**
+     * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+     *
+     * This is done to avoid a race condition between the last transaction applied in
+     * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a
+     * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it
+     * gets applied before the transaction created by the last onAnimationUpdate. As a result of
+     * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which
+     * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize
+     * operation until the next frame. This aligns the last onAnimationUpdate transaction with the
+     * WCT application.
+     *
+     * The race only happens when the PiP surface transaction has to be synced with the PiP menu
+     * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and
+     * the PiP menu contents.
+     */
+    private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+        if (!shouldSyncPipTransactionWithMenu()) {
+            finishResizeRunnable.run();
+            return;
+        }
+        mPipMenuController.runWithNextFrame(finishResizeRunnable);
+    }
+
+    private boolean shouldSyncPipTransactionWithMenu() {
+        return mPipMenuController.isMenuVisible();
+    }
+
     @VisibleForTesting
     final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
             new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +251,7 @@
                 @Override
                 public boolean handlePipTransaction(SurfaceControl leash,
                         SurfaceControl.Transaction tx, Rect destinationBounds) {
-                    if (mPipMenuController.isMenuVisible()) {
+                    if (shouldSyncPipTransactionWithMenu()) {
                         mPipMenuController.movePipMenu(leash, tx, destinationBounds);
                         return true;
                     }
@@ -1223,7 +1253,7 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, toBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
@@ -1265,7 +1295,7 @@
         mSurfaceTransactionHelper
                 .scale(tx, mLeash, startBounds, toBounds, degrees)
                 .round(tx, mLeash, startBounds, toBounds);
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.movePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index a0a8f9f..531b9de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -305,6 +305,18 @@
                 showResizeHandle);
     }
 
+    @Override
+    public void runWithNextFrame(Runnable runnable) {
+        if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+            runnable.run();
+        }
+
+        mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+            mMainHandler.post(runnable);
+        });
+        mPipMenuView.invalidate();
+    }
+
     /**
      * Move the PiP menu, which does a translation and possibly a scale transformation.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 616d447..d28a9f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -612,9 +612,24 @@
                 new DisplayInsetsController.OnInsetsChangedListener() {
                     @Override
                     public void insetsChanged(InsetsState insetsState) {
+                        int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
                         onDisplayChanged(
                                 mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
                                 false /* saveRestoreSnapFraction */);
+                        int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
+                        if (!mEnablePipKeepClearAlgorithm) {
+                            int pipTop = mPipBoundsState.getBounds().top;
+                            int diff = newMaxMovementBound - oldMaxMovementBound;
+                            if (diff < 0 && pipTop > newMaxMovementBound) {
+                                // bottom inset has increased, move PiP up if it is too low
+                                mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
+                                        newMaxMovementBound - pipTop);
+                            }
+                            if (diff > 0 && oldMaxMovementBound == pipTop) {
+                                // bottom inset has decreased, move PiP down if it was by the edge
+                                mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
+                            }
+                        }
                     }
                 });
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 975d4bb..a9a97be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -427,7 +427,7 @@
         // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
         // occluded by the IME or shelf.
         if (fromImeAdjustment || fromShelfAdjustment) {
-            if (mTouchState.isUserInteracting()) {
+            if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
                 // Defer the update of the current movement bounds until after the user finishes
                 // touching the screen
             } else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ab7edbf..c39400a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -421,6 +421,18 @@
     }
 
     @Override
+    public void runWithNextFrame(Runnable runnable) {
+        if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+            runnable.run();
+        }
+
+        mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+            mMainHandler.post(runnable);
+        });
+        mPipMenuView.invalidate();
+    }
+
+    @Override
     public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
             Rect pipDestBounds) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 89205a6..519ec14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -175,6 +175,9 @@
     }
 
     private void onInit() {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mOrganizer.shareTransactionQueue();
+        }
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
                 this::createExternalInterface, this);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index dca516a..36dd8ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -26,11 +26,16 @@
 import android.content.Context;
 import android.hardware.input.InputManager;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Choreographer;
+import android.view.InputChannel;
 import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -64,8 +69,11 @@
     private final SyncTransactionQueue mSyncQueue;
     private FreeformTaskTransitionStarter mTransitionStarter;
     private DesktopModeController mDesktopModeController;
+    private EventReceiver mEventReceiver;
+    private InputMonitor mInputMonitor;
 
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+    private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
 
     public CaptionWindowDecorViewModel(
             Context context,
@@ -108,12 +116,19 @@
                 mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
-        TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
+        TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
+                mDragStartListener);
         CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
         windowDecoration.setDragResizeCallback(taskPositioner);
         setupWindowDecorationForTransition(taskInfo, startT, finishT);
+        if (mInputMonitor == null) {
+            mInputMonitor = InputManager.getInstance().monitorGestureInput(
+                    "caption-touch", mContext.getDisplayId());
+            mEventReceiver = new EventReceiver(
+                    mInputMonitor.getInputChannel(), Looper.myLooper());
+        }
         return true;
     }
 
@@ -165,6 +180,7 @@
 
         @Override
         public void onClick(View v) {
+            CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final int id = v.getId();
             if (id == R.id.close_window) {
                 WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -176,6 +192,15 @@
                 }
             } else if (id == R.id.back_button) {
                 injectBackKey();
+            } else if (id == R.id.caption_handle) {
+                decoration.createHandleMenu();
+            } else if (id == R.id.desktop_button) {
+                mDesktopModeController.setDesktopModeActive(true);
+                decoration.closeHandleMenu();
+            } else if (id == R.id.fullscreen_button) {
+                mDesktopModeController.setDesktopModeActive(false);
+                decoration.closeHandleMenu();
+                decoration.setButtonVisibility();
             }
         }
         private void injectBackKey() {
@@ -257,6 +282,36 @@
         }
     }
 
+    // InputEventReceiver to listen for touch input outside of caption bounds
+    private class EventReceiver extends InputEventReceiver {
+        EventReceiver(InputChannel channel, Looper looper) {
+            super(channel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            boolean handled = false;
+            if (event instanceof MotionEvent
+                    && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
+                handled = true;
+                CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
+            }
+            finishInputEvent(event, handled);
+        }
+    }
+
+    // If any input received is outside of caption bounds, turn off handle menu
+    private void handleMotionEvent(MotionEvent ev) {
+        int size = mWindowDecorByTaskId.size();
+        for (int i = 0; i < size; i++) {
+            CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+            if (decoration != null) {
+                decoration.closeHandleMenuIfNeeded(ev);
+            }
+        }
+    }
+
+
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
         return DesktopModeStatus.IS_SUPPORTED
@@ -264,4 +319,11 @@
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
+
+    private class DragStartListenerImpl implements TaskPositioner.DragStartListener{
+        @Override
+        public void onDragStart(int taskId) {
+            mWindowDecorByTaskId.get(taskId).closeHandleMenu();
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 9d61c14..03cad04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -20,10 +20,14 @@
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
 import android.view.Choreographer;
+import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -58,6 +62,8 @@
 
     private boolean mDesktopActive;
 
+    private AdditionalWindow mHandleMenu;
+
     CaptionWindowDecoration(
             Context context,
             DisplayController displayController,
@@ -123,7 +129,20 @@
         if (isDragResizeable) {
             mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
         }
+        final Resources resources = mDecorWindowContext.getResources();
+        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        final int captionHeight = loadDimensionPixelSize(resources,
+                mRelayoutParams.mCaptionHeightId);
+        final int captionWidth = loadDimensionPixelSize(resources,
+                mRelayoutParams.mCaptionWidthId);
+        final int captionLeft = taskBounds.width() / 2
+                - captionWidth / 2;
+        final int captionTop = taskBounds.top
+                <= captionHeight / 2 ? 0 : -captionHeight / 2;
+        mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
+
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+        taskInfo = null; // Clear it just in case we use it accidentally
 
         mTaskOrganizer.applyTransaction(wct);
 
@@ -137,15 +156,14 @@
         }
 
         // If this task is not focused, do not show caption.
-        setCaptionVisibility(taskInfo.isFocused);
+        setCaptionVisibility(mTaskInfo.isFocused);
 
         // Only handle should show if Desktop Mode is inactive.
         boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
-        if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) {
+        if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
             mDesktopActive = desktopCurrentStatus;
             setButtonVisibility();
         }
-        taskInfo = null; // Clear it just in case we use it accidentally
 
         if (!isDragResizeable) {
             closeDragResizeListener();
@@ -184,9 +202,22 @@
         back.setOnClickListener(mOnCaptionButtonClickListener);
         View handle = caption.findViewById(R.id.caption_handle);
         handle.setOnTouchListener(mOnCaptionTouchListener);
+        handle.setOnClickListener(mOnCaptionButtonClickListener);
         setButtonVisibility();
     }
 
+    private void setupHandleMenu() {
+        View menu = mHandleMenu.mWindowViewHost.getView();
+        View fullscreen = menu.findViewById(R.id.fullscreen_button);
+        fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
+        View desktop = menu.findViewById(R.id.desktop_button);
+        desktop.setOnClickListener(mOnCaptionButtonClickListener);
+        View split = menu.findViewById(R.id.split_screen_button);
+        split.setOnClickListener(mOnCaptionButtonClickListener);
+        View more = menu.findViewById(R.id.more_button);
+        more.setOnClickListener(mOnCaptionButtonClickListener);
+    }
+
     /**
      * Sets caption visibility based on task focus.
      *
@@ -194,8 +225,9 @@
      */
     private void setCaptionVisibility(boolean visible) {
         int v = visible ? View.VISIBLE : View.GONE;
-        View caption = mResult.mRootView.findViewById(R.id.caption);
-        caption.setVisibility(v);
+        View captionView = mResult.mRootView.findViewById(R.id.caption);
+        captionView.setVisibility(v);
+        if (!visible) closeHandleMenu();
     }
 
     /**
@@ -203,6 +235,7 @@
      *
      */
     public void setButtonVisibility() {
+        mDesktopActive = DesktopModeStatus.isActive(mContext);
         int v = mDesktopActive ? View.VISIBLE : View.GONE;
         View caption = mResult.mRootView.findViewById(R.id.caption);
         View back = caption.findViewById(R.id.back_button);
@@ -220,6 +253,10 @@
         caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
     }
 
+    public boolean isHandleMenuActive() {
+        return mHandleMenu != null;
+    }
+
     private void closeDragResizeListener() {
         if (mDragResizeListener == null) {
             return;
@@ -228,9 +265,67 @@
         mDragResizeListener = null;
     }
 
+    /**
+     * Create and display handle menu window
+     */
+    public void createHandleMenu() {
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        final Resources resources = mDecorWindowContext.getResources();
+        int x = mRelayoutParams.mCaptionX;
+        int y = mRelayoutParams.mCaptionY;
+        int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+        int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+        String namePrefix = "Caption Menu";
+        mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
+                x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
+                width, height);
+        mSyncQueue.runInSync(transaction -> {
+            transaction.merge(t);
+            t.close();
+        });
+        setupHandleMenu();
+    }
+
+    /**
+     * Close the handle menu window
+     */
+    public void closeHandleMenu() {
+        if (!isHandleMenuActive()) return;
+        mHandleMenu.releaseView();
+        mHandleMenu = null;
+    }
+
+    @Override
+    void releaseViews() {
+        closeHandleMenu();
+        super.releaseViews();
+    }
+
+    /**
+     * Close an open handle menu if input is outside of menu coordinates
+     * @param ev the tapped point to compare against
+     * @return
+     */
+    public void closeHandleMenuIfNeeded(MotionEvent ev) {
+        if (mHandleMenu != null) {
+            Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+                    .positionInParent;
+            final Resources resources = mDecorWindowContext.getResources();
+            ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+            ev.offsetLocation(-positionInParent.x, -positionInParent.y);
+            int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+            int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+            if (!(ev.getX() >= 0 && ev.getY()  >= 0
+                    && ev.getX()  <= width && ev.getY()  <= height)) {
+                closeHandleMenu();
+            }
+        }
+    }
+
     @Override
     public void close() {
         closeDragResizeListener();
+        closeHandleMenu();
         super.close();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 27c1011..f0f2db7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -42,14 +42,18 @@
     private final Rect mResizeTaskBounds = new Rect();
 
     private int mCtrlType;
+    private DragStartListener mDragStartListener;
 
-    TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
+    TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+            DragStartListener dragStartListener) {
         mTaskOrganizer = taskOrganizer;
         mWindowDecoration = windowDecoration;
+        mDragStartListener = dragStartListener;
     }
 
     @Override
     public void onDragResizeStart(int ctrlType, float x, float y) {
+        mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
         mCtrlType = ctrlType;
 
         mTaskBoundsAtDragStart.set(
@@ -97,4 +101,12 @@
             mTaskOrganizer.applyTransaction(wct);
         }
     }
+
+    interface DragStartListener {
+        /**
+         * Inform the implementing class that a drag resize has started
+         * @param taskId id of this positioner's {@link WindowDecoration}
+         */
+        void onDragStart(int taskId);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index b314163..7ecb3f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -200,16 +200,17 @@
 
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
         final Resources resources = mDecorWindowContext.getResources();
-        final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
-        final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
+        outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+        outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
         outResult.mWidth = taskBounds.width()
                 + loadDimensionPixelSize(resources, params.mOutsetRightId)
-                - decorContainerOffsetX;
+                - outResult.mDecorContainerOffsetX;
         outResult.mHeight = taskBounds.height()
                 + loadDimensionPixelSize(resources, params.mOutsetBottomId)
-                - decorContainerOffsetY;
+                - outResult.mDecorContainerOffsetY;
         startT.setPosition(
-                        mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
+                        mDecorationContainerSurface,
+                        outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
                 .setWindowCrop(mDecorationContainerSurface,
                         outResult.mWidth, outResult.mHeight)
                 // TODO(b/244455401): Change the z-order when it's better organized
@@ -252,14 +253,11 @@
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
 
-        //Prevent caption from going offscreen if task is too high up
-        final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
-
         startT.setPosition(
-                        mCaptionContainerSurface, -decorContainerOffsetX
-                                + taskBounds.width() / 2 - captionWidth / 2,
-                        -decorContainerOffsetY - captionYPos)
-                .setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
+                        mCaptionContainerSurface,
+                        -outResult.mDecorContainerOffsetX + params.mCaptionX,
+                        -outResult.mDecorContainerOffsetY + params.mCaptionY)
+                .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
                 .show(mCaptionContainerSurface);
 
         if (mCaptionWindowManager == null) {
@@ -292,7 +290,7 @@
             // Caption insets
             mCaptionInsetsRect.set(taskBounds);
             mCaptionInsetsRect.bottom =
-                    mCaptionInsetsRect.top + captionHeight - captionYPos;
+                    mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
             wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
                     CAPTION_INSETS_TYPES);
         } else {
@@ -302,10 +300,10 @@
         // Task surface itself
         Point taskPosition = mTaskInfo.positionInParent;
         mTaskSurfaceCrop.set(
-                decorContainerOffsetX,
-                decorContainerOffsetY,
-                outResult.mWidth + decorContainerOffsetX,
-                outResult.mHeight + decorContainerOffsetY);
+                outResult.mDecorContainerOffsetX,
+                outResult.mDecorContainerOffsetY,
+                outResult.mWidth + outResult.mDecorContainerOffsetX,
+                outResult.mHeight + outResult.mDecorContainerOffsetY);
         startT.show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
                 .setCrop(mTaskSurface, mTaskSurfaceCrop);
@@ -326,7 +324,7 @@
         return true;
     }
 
-    private void releaseViews() {
+    void releaseViews() {
         if (mViewHost != null) {
             mViewHost.release();
             mViewHost = null;
@@ -369,20 +367,60 @@
         releaseViews();
     }
 
-    private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+    static int loadDimensionPixelSize(Resources resources, int resourceId) {
         if (resourceId == Resources.ID_NULL) {
             return 0;
         }
         return resources.getDimensionPixelSize(resourceId);
     }
 
-    private static float loadDimension(Resources resources, int resourceId) {
+    static float loadDimension(Resources resources, int resourceId) {
         if (resourceId == Resources.ID_NULL) {
             return 0;
         }
         return resources.getDimension(resourceId);
     }
 
+    /**
+     * Create a window associated with this WindowDecoration.
+     * Note that subclass must dispose of this when the task is hidden/closed.
+     * @param layoutId layout to make the window from
+     * @param t the transaction to apply
+     * @param xPos x position of new window
+     * @param yPos y position of new window
+     * @param width width of new window
+     * @param height height of new window
+     * @return
+     */
+    AdditionalWindow addWindow(int layoutId, String namePrefix,
+            SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
+        final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+        SurfaceControl windowSurfaceControl = builder
+                .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
+                .setContainerLayer()
+                .setParent(mDecorationContainerSurface)
+                .build();
+        View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+
+        t.setPosition(
+                windowSurfaceControl, xPos, yPos)
+                .setWindowCrop(windowSurfaceControl, width, height)
+                .show(windowSurfaceControl);
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(width, height,
+                        WindowManager.LayoutParams.TYPE_APPLICATION,
+                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+        lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
+        lp.setTrustedOverlay();
+        WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
+                windowSurfaceControl, null /* hostInputToken */);
+        SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
+                .create(mDecorWindowContext, mDisplay, windowManager);
+        viewHost.setView(v, lp);
+        return new AdditionalWindow(windowSurfaceControl, viewHost,
+                mSurfaceControlTransactionSupplier);
+    }
+
     static class RelayoutParams{
         RunningTaskInfo mRunningTaskInfo;
         int mLayoutResId;
@@ -395,6 +433,9 @@
         int mOutsetLeftId;
         int mOutsetRightId;
 
+        int mCaptionX;
+        int mCaptionY;
+
         void setOutsets(int leftId, int topId, int rightId, int bottomId) {
             mOutsetLeftId = leftId;
             mOutsetTopId = topId;
@@ -402,6 +443,11 @@
             mOutsetBottomId = bottomId;
         }
 
+        void setCaptionPosition(int left, int top) {
+            mCaptionX = left;
+            mCaptionY = top;
+        }
+
         void reset() {
             mLayoutResId = Resources.ID_NULL;
             mCaptionHeightId = Resources.ID_NULL;
@@ -412,6 +458,9 @@
             mOutsetBottomId = Resources.ID_NULL;
             mOutsetLeftId = Resources.ID_NULL;
             mOutsetRightId = Resources.ID_NULL;
+
+            mCaptionX = 0;
+            mCaptionY = 0;
         }
     }
 
@@ -419,10 +468,14 @@
         int mWidth;
         int mHeight;
         T mRootView;
+        int mDecorContainerOffsetX;
+        int mDecorContainerOffsetY;
 
         void reset() {
             mWidth = 0;
             mHeight = 0;
+            mDecorContainerOffsetX = 0;
+            mDecorContainerOffsetY = 0;
             mRootView = null;
         }
     }
@@ -432,4 +485,41 @@
             return new SurfaceControlViewHost(c, d, wmm);
         }
     }
+
+    /**
+     * Subclass for additional windows associated with this WindowDecoration
+     */
+    static class AdditionalWindow {
+        SurfaceControl mWindowSurface;
+        SurfaceControlViewHost mWindowViewHost;
+        Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+        private AdditionalWindow(SurfaceControl surfaceControl,
+                SurfaceControlViewHost surfaceControlViewHost,
+                Supplier<SurfaceControl.Transaction> transactionSupplier) {
+            mWindowSurface = surfaceControl;
+            mWindowViewHost = surfaceControlViewHost;
+            mTransactionSupplier = transactionSupplier;
+        }
+
+        void releaseView() {
+            WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
+
+            if (mWindowViewHost != null) {
+                mWindowViewHost.release();
+                mWindowViewHost = null;
+            }
+            windowManager = null;
+            final SurfaceControl.Transaction t = mTransactionSupplier.get();
+            boolean released = false;
+            if (mWindowSurface != null) {
+                t.remove(mWindowSurface);
+                mWindowSurface = null;
+                released = true;
+            }
+            if (released) {
+                t.apply();
+            }
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index f4efc37..1c28c3d 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -6,3 +6,4 @@
 lbill@google.com
 madym@google.com
 hwwang@google.com
+chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 6f1ff99..8765ad1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -45,14 +45,23 @@
 fun FlickerTestParameter.splitScreenEntered(
     component1: IComponentMatcher,
     component2: IComponentMatcher,
-    fromOtherApp: Boolean
+    fromOtherApp: Boolean,
+    appExistAtStart: Boolean = true
 ) {
     if (fromOtherApp) {
-        appWindowIsInvisibleAtStart(component1)
+        if (appExistAtStart) {
+            appWindowIsInvisibleAtStart(component1)
+        } else {
+            appWindowIsNotContainAtStart(component1)
+        }
     } else {
         appWindowIsVisibleAtStart(component1)
     }
-    appWindowIsInvisibleAtStart(component2)
+    if (appExistAtStart) {
+        appWindowIsInvisibleAtStart(component2)
+    } else {
+        appWindowIsNotContainAtStart(component2)
+    }
     splitScreenDividerIsInvisibleAtStart()
 
     appWindowIsVisibleAtEnd(component1)
@@ -315,6 +324,10 @@
     assertWmEnd { this.isAppWindowInvisible(component) }
 }
 
+fun FlickerTestParameter.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+    assertWmStart { this.notContains(component) }
+}
+
 fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
     assertWm { this.isAppWindowVisible(component) }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 0000000..e9c765a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.view.WindowManagerPolicyConstants
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging a shortcut.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenByDragFromShortcut(
+    testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(testSpec.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                tapl.goHome()
+                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+                primaryApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                tapl.launchedAppState.taskbar
+                    .getAppIcon(secondaryApp.appName)
+                    .openDeepShortcutMenu()
+                    .getMenuItem("Split Screen Secondary Activity")
+                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    @Postsubmit
+    @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp,
+        fromOtherApp = false, appExistAtStart = false)
+
+    @Postsubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Postsubmit
+    @Test
+    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+
+    @Postsubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @Postsubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
+
+    @Postsubmit
+    @Test
+    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
+        secondaryApp)
+
+    @Postsubmit
+    @Test
+    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+
+    @Postsubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() {
+        testSpec.assertWm {
+            this.notContains(secondaryApp)
+                .then()
+                .isAppWindowInvisible(secondaryApp, isOptional = true)
+                .then()
+                .isAppWindowVisible(secondaryApp)
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun entireScreenCovered() =
+        super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+        super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+        super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+        super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes =
+                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
index f3f7067..11948dbf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -24,6 +24,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 
@@ -41,11 +42,13 @@
     private BackgroundWindowManager mBackgroundWindowManager;
     @Mock
     private DisplayLayout  mMockDisplayLayout;
+    @Mock
+    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mBackgroundWindowManager = new BackgroundWindowManager(mContext);
+        mBackgroundWindowManager = new BackgroundWindowManager(mContext, mRootDisplayAreaOrganizer);
         mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 4d37e5d..15181b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -35,6 +35,7 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -64,6 +65,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -102,12 +104,14 @@
     private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
     private SurfaceControl.Transaction mMockSurfaceControlStartT;
     private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+    private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
     private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
 
     @Before
     public void setUp() {
         mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
         mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
+        mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
 
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
                 .create(any(), any(), any());
@@ -227,8 +231,8 @@
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
-        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
-        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
         verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -242,7 +246,7 @@
             verify(mMockView).setTaskFocusState(true);
             verify(mMockWindowContainerTransaction)
                     .addRectInsetsProvider(taskInfo.token,
-                            new Rect(100, 300, 400, 332),
+                            new Rect(100, 300, 400, 364),
                             new int[] { InsetsState.ITYPE_CAPTION_BAR });
         }
 
@@ -366,6 +370,71 @@
         verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
     }
 
+    @Test
+    public void testAddWindow() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder decorContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(decorContainerSurface);
+        mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+        final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+                createMockSurfaceControlBuilder(taskBackgroundSurface);
+        mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder captionContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(captionContainerSurface);
+        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+        final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mMockSurfaceControlTransactions.add(t);
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder()
+                        .setBackgroundColor(Color.YELLOW);
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setBounds(TASK_BOUNDS)
+                .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+                .setVisible(true)
+                .build();
+        taskInfo.isFocused = true;
+        taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+        mRelayoutParams.setOutsets(
+                R.dimen.test_window_decor_left_outset,
+                R.dimen.test_window_decor_top_outset,
+                R.dimen.test_window_decor_right_outset,
+                R.dimen.test_window_decor_bottom_outset);
+        final SurfaceControl taskSurface = mock(SurfaceControl.class);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        windowDecor.relayout(taskInfo);
+
+        final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder additionalWindowSurfaceBuilder =
+                createMockSurfaceControlBuilder(additionalWindowSurface);
+        mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder);
+
+        WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow();
+
+        verify(additionalWindowSurfaceBuilder).setContainerLayer();
+        verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
+        verify(additionalWindowSurfaceBuilder).build();
+        verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+        verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
+        verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
+        verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
+                .create(any(), eq(defaultDisplay), any());
+        assertThat(additionalWindow.mWindowViewHost).isNotNull();
+
+        additionalWindow.releaseView();
+
+        assertThat(additionalWindow.mWindowViewHost).isNull();
+        assertThat(additionalWindow.mWindowSurface).isNull();
+    }
+
     private TestWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
         return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -429,5 +498,20 @@
             relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
                     mMockWindowContainerTransaction, mMockView, mRelayoutResult);
         }
+
+        private WindowDecoration.AdditionalWindow addTestWindow() {
+            final Resources resources = mDecorWindowContext.getResources();
+            int x = mRelayoutParams.mCaptionX;
+            int y = mRelayoutParams.mCaptionY;
+            int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+            int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+            String name = "Test Window";
+            WindowDecoration.AdditionalWindow additionalWindow =
+                    addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
+                            x - mRelayoutResult.mDecorContainerOffsetX,
+                            y - mRelayoutResult.mDecorContainerOffsetY,
+                            width, height);
+            return additionalWindow;
+        }
     }
 }
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index 15be80c..d1cfd03 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <memory>
 #include <utility>
 
 #include "androidfw/CursorWindow.h"
@@ -184,7 +185,7 @@
     ASSERT_EQ(w->allocRow(), OK);
 
     // Scratch buffer that will fit before inflation
-    void* buf = malloc(kHalfInlineSize);
+    char buf[kHalfInlineSize];
 
     // Store simple value
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -262,7 +263,7 @@
     ASSERT_EQ(w->allocRow(), OK);
 
     // Scratch buffer that will fit before inflation
-    void* buf = malloc(kHalfInlineSize);
+    char buf[kHalfInlineSize];
 
     // Store simple value
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -322,7 +323,8 @@
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
 
     // Store object that forces inflation
-    void* buf = malloc(kGiantSize);
+    std::unique_ptr<char> bufPtr(new char[kGiantSize]);
+    void* buf = bufPtr.get();
     memset(buf, 42, kGiantSize);
     ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK);
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 29f3773..cccc0f81 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -678,6 +678,7 @@
     srcs: [
         "tests/unit/main.cpp",
         "tests/unit/ABitmapTests.cpp",
+        "tests/unit/AutoBackendTextureReleaseTests.cpp",
         "tests/unit/CacheManagerTests.cpp",
         "tests/unit/CanvasContextTests.cpp",
         "tests/unit/CanvasOpTests.cpp",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index ef5eacb..b656b6a 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -32,9 +32,17 @@
     bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
     GrBackendFormat backendFormat =
             GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+    LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
+                        __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
+                                 ", AHardwareBuffer_Format==%" PRIu32 ".",
+                        static_cast<int>(context->backend()), desc.format);
     mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
             context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
             createProtectedImage, backendFormat, false);
+    LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
+                        __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
+                                 ", protected==%d",
+                        desc.width, desc.height, createProtectedImage);
 }
 
 void AutoBackendTextureRelease::unref(bool releaseImage) {
@@ -74,13 +82,13 @@
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+    // The following ref will be counteracted by Skia calling releaseProc, either during
+    // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
+    // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+    ref();
     mImage = SkImage::MakeFromTexture(
             context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
             uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
-    if (mImage.get()) {
-        // The following ref will be counteracted by releaseProc, when SkImage is discarded.
-        ref();
-    }
 }
 
 void AutoBackendTextureRelease::newBufferContent(GrDirectContext* context) {
diff --git a/libs/hwui/AutoBackendTextureRelease.h b/libs/hwui/AutoBackendTextureRelease.h
index c9bb767..f0eb2a8 100644
--- a/libs/hwui/AutoBackendTextureRelease.h
+++ b/libs/hwui/AutoBackendTextureRelease.h
@@ -25,6 +25,9 @@
 namespace android {
 namespace uirenderer {
 
+// Friend TestUtils serves as a proxy for any test cases that require access to private members.
+class TestUtils;
+
 /**
  * AutoBackendTextureRelease manages EglImage/VkImage lifetime. It is a ref-counted object
  * that keeps GPU resources alive until the last SkImage object using them is destroyed.
@@ -66,6 +69,9 @@
 
     // mImage is the SkImage created from mBackendTexture.
     sk_sp<SkImage> mImage;
+
+    // Friend TestUtils serves as a proxy for any test cases that require access to private members.
+    friend class TestUtils;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 35258a3..0513447 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -20,7 +20,6 @@
 #include <android/api-level.h>
 #else
 #define __ANDROID_API_P__ 28
-#define __ANDROID_API_U__ 34
 #endif
 #include <androidfw/ResourceTypes.h>
 #include <hwui/Canvas.h>
@@ -31,9 +30,8 @@
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedStringChars.h>
 
-#include "Bitmap.h"
 #include "FontUtils.h"
-#include "SkAndroidFrameworkUtils.h"
+#include "Bitmap.h"
 #include "SkBitmap.h"
 #include "SkBlendMode.h"
 #include "SkClipOp.h"
@@ -44,10 +42,10 @@
 #include "SkMatrix.h"
 #include "SkPath.h"
 #include "SkPoint.h"
-#include "SkRRect.h"
 #include "SkRect.h"
 #include "SkRefCnt.h"
 #include "SkRegion.h"
+#include "SkRRect.h"
 #include "SkScalar.h"
 #include "SkVertices.h"
 
@@ -712,9 +710,6 @@
 
 static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) {
     Canvas::setCompatibilityVersion(apiLevel);
-    if (apiLevel < __ANDROID_API_U__) {
-        SkAndroidFrameworkUtils::UseLegacyLocalMatrixConcatenation();
-    }
 }
 
 static void punchHole(JNIEnv* env, jobject, jlong canvasPtr, jfloat left, jfloat top, jfloat right,
@@ -805,6 +800,7 @@
     ret |= RegisterMethodsOrDie(env, "android/graphics/BaseCanvas", gDrawMethods, NELEM(gDrawMethods));
     ret |= RegisterMethodsOrDie(env, "android/graphics/BaseRecordingCanvas", gDrawMethods, NELEM(gDrawMethods));
     return ret;
+
 }
 
 }; // namespace android
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index f2282e66..1a47db5 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -229,10 +229,10 @@
     // TODO should we let the bound of the drawable do this for us?
     const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
     bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds);
-    auto clipBounds = canvas->getLocalClipBounds();
-    SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
-    SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
     if (!quickRejected) {
+        auto clipBounds = canvas->getLocalClipBounds();
+        SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
+        SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
         SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl();
         const LayerProperties& layerProperties = properties.layerProperties();
         // composing a hardware layer
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 75865c7..9d5c13e 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <AutoBackendTextureRelease.h>
 #include <DisplayList.h>
 #include <Matrix.h>
 #include <Properties.h>
@@ -293,6 +294,11 @@
     static SkRect getClipBounds(const SkCanvas* canvas);
     static SkRect getLocalClipBounds(const SkCanvas* canvas);
 
+    static int getUsageCount(const AutoBackendTextureRelease* textureRelease) {
+        EXPECT_NE(nullptr, textureRelease);
+        return textureRelease->mUsageCount;
+    }
+
     struct CallCounts {
         int sync = 0;
         int contextDestroyed = 0;
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
new file mode 100644
index 0000000..2ec78a4
--- /dev/null
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "AutoBackendTextureRelease.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+AHardwareBuffer* allocHardwareBuffer() {
+    AHardwareBuffer* buffer;
+    AHardwareBuffer_Desc desc = {
+            .width = 16,
+            .height = 16,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
+    };
+    constexpr int kSucceeded = 0;
+    int status = AHardwareBuffer_allocate(&desc, &buffer);
+    EXPECT_EQ(kSucceeded, status);
+    return buffer;
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_invalid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_invalid) {
+    AHardwareBuffer* buffer = allocHardwareBuffer();
+    AutoBackendTextureRelease* textureRelease =
+            new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+    textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->unref(true);
+    AHardwareBuffer_release(buffer);
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_valid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_valid) {
+    AHardwareBuffer* buffer = allocHardwareBuffer();
+    AutoBackendTextureRelease* textureRelease =
+            new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, renderThread.getGrContext());
+
+    EXPECT_EQ(2, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->unref(true);
+    AHardwareBuffer_release(buffer);
+}
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index b38f9ea..98578b2 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -24,6 +24,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -123,20 +125,23 @@
      * @hide
      */
     public static GnssCapabilities empty() {
-        return new GnssCapabilities(0, 0, 0);
+        return new GnssCapabilities(0, 0, 0, new ArrayList<>());
     }
 
     private final @TopHalCapabilityFlags int mTopFlags;
     private final @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
     private final @SubHalPowerCapabilityFlags int mPowerFlags;
+    private final @NonNull List<GnssSignalType> mGnssSignalTypes;
 
     private GnssCapabilities(
             @TopHalCapabilityFlags int topFlags,
             @SubHalMeasurementCorrectionsCapabilityFlags int measurementCorrectionsFlags,
-            @SubHalPowerCapabilityFlags int powerFlags) {
+            @SubHalPowerCapabilityFlags int powerFlags,
+            @NonNull List<GnssSignalType> gnssSignalTypes) {
         mTopFlags = topFlags;
         mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
         mPowerFlags = powerFlags;
+        mGnssSignalTypes = gnssSignalTypes;
     }
 
     /**
@@ -148,7 +153,8 @@
         if (mTopFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags);
+            return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
+                    new ArrayList<>(mGnssSignalTypes));
         }
     }
 
@@ -163,7 +169,8 @@
         if (mMeasurementCorrectionsFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, flags, mPowerFlags);
+            return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
+                    new ArrayList<>(mGnssSignalTypes));
         }
     }
 
@@ -177,7 +184,8 @@
         if (mPowerFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags);
+            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
+                    new ArrayList<>(mGnssSignalTypes));
         }
     }
 
@@ -424,6 +432,14 @@
         return (mPowerFlags & SUB_HAL_POWER_CAPABILITY_OTHER_MODES) != 0;
     }
 
+    /**
+     * Returns the list of {@link GnssSignalType}s that the GNSS chipset supports.
+     */
+    @NonNull
+    public List<GnssSignalType> getGnssSignalTypes() {
+        return mGnssSignalTypes;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -441,14 +457,15 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags);
+        return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags, mGnssSignalTypes);
     }
 
     public static final @NonNull Creator<GnssCapabilities> CREATOR =
             new Creator<GnssCapabilities>() {
                 @Override
                 public GnssCapabilities createFromParcel(Parcel in) {
-                    return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt());
+                    return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt(),
+                            in.createTypedArrayList(GnssSignalType.CREATOR));
                 }
 
                 @Override
@@ -467,6 +484,7 @@
         parcel.writeInt(mTopFlags);
         parcel.writeInt(mMeasurementCorrectionsFlags);
         parcel.writeInt(mPowerFlags);
+        parcel.writeTypedList(mGnssSignalTypes);
     }
 
     @Override
@@ -545,6 +563,9 @@
         if (hasPowerOtherModes()) {
             builder.append("OTHER_MODES_POWER ");
         }
+        if (!mGnssSignalTypes.isEmpty()) {
+            builder.append("signalTypes=").append(mGnssSignalTypes).append(" ");
+        }
         if (builder.length() > 1) {
             builder.setLength(builder.length() - 1);
         } else {
@@ -562,17 +583,20 @@
         private @TopHalCapabilityFlags int mTopFlags;
         private @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
         private @SubHalPowerCapabilityFlags int mPowerFlags;
+        private @NonNull List<GnssSignalType> mGnssSignalTypes;
 
         public Builder() {
             mTopFlags = 0;
             mMeasurementCorrectionsFlags = 0;
             mPowerFlags = 0;
+            mGnssSignalTypes = new ArrayList<>();
         }
 
         public Builder(@NonNull GnssCapabilities capabilities) {
             mTopFlags = capabilities.mTopFlags;
             mMeasurementCorrectionsFlags = capabilities.mMeasurementCorrectionsFlags;
             mPowerFlags = capabilities.mPowerFlags;
+            mGnssSignalTypes = capabilities.mGnssSignalTypes;
         }
 
         /**
@@ -779,10 +803,19 @@
         }
 
         /**
+         * Sets a list of {@link GnssSignalType}.
+         */
+        public @NonNull Builder setGnssSignalTypes(@NonNull List<GnssSignalType> gnssSignalTypes) {
+            mGnssSignalTypes = gnssSignalTypes;
+            return this;
+        }
+
+        /**
          * Builds a new GnssCapabilities.
          */
         public @NonNull GnssCapabilities build() {
-            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags);
+            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags,
+                    new ArrayList<>(mGnssSignalTypes));
         }
 
         private static int setFlag(int value, int flag, boolean set) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/location/java/android/location/GnssSignalType.aidl
similarity index 73%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to location/java/android/location/GnssSignalType.aidl
index 1550ab3..1c43fe5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/location/java/android/location/GnssSignalType.aidl
@@ -1,11 +1,11 @@
 /*
- * 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package android.location;
 
-parcelable RemoteTransitionCompat;
+parcelable GnssSignalType;
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
new file mode 100644
index 0000000..f9c6e72
--- /dev/null
+++ b/location/java/android/location/GnssSignalType.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * This class represents a GNSS signal type.
+ */
+public final class GnssSignalType implements Parcelable {
+
+    /**
+     * Creates a {@link GnssSignalType} with a full list of parameters.
+     *
+     * @param constellationType the constellation type as defined in
+     * {@link GnssStatus.ConstellationType}
+     * @param carrierFrequencyHz the carrier frequency in Hz
+     * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
+     */
+    @NonNull
+    public static GnssSignalType create(@GnssStatus.ConstellationType int constellationType,
+            @FloatRange(from = 0.0f, fromInclusive = false) double carrierFrequencyHz,
+            @NonNull String codeType) {
+        Preconditions.checkArgument(carrierFrequencyHz > 0,
+                "carrierFrequencyHz must be greater than 0.");
+        Objects.requireNonNull(codeType);
+        return new GnssSignalType(constellationType, carrierFrequencyHz, codeType);
+    }
+
+    @GnssStatus.ConstellationType
+    private final int mConstellationType;
+    @FloatRange(from = 0.0f, fromInclusive = false)
+    private final double mCarrierFrequencyHz;
+    @NonNull
+    private final String mCodeType;
+
+    /**
+     * Creates a {@link GnssSignalType} with a full list of parameters.
+     */
+    private GnssSignalType(@GnssStatus.ConstellationType int constellationType,
+            double carrierFrequencyHz, @NonNull String codeType) {
+        this.mConstellationType = constellationType;
+        this.mCarrierFrequencyHz = carrierFrequencyHz;
+        this.mCodeType = codeType;
+    }
+
+    /** Returns the {@link GnssStatus.ConstellationType}. */
+    @GnssStatus.ConstellationType
+    public int getConstellationType() {
+        return mConstellationType;
+    }
+
+    /** Returns the carrier frequency in Hz. */
+    @FloatRange(from = 0.0f, fromInclusive = false)
+    public double getCarrierFrequencyHz() {
+        return mCarrierFrequencyHz;
+    }
+
+    /**
+     * Return the code type.
+     *
+     * @see GnssMeasurement#getCodeType()
+     */
+    @NonNull
+    public String getCodeType() {
+        return mCodeType;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<GnssSignalType> CREATOR =
+            new Parcelable.Creator<GnssSignalType>() {
+                @Override
+                @NonNull
+                public GnssSignalType createFromParcel(@NonNull Parcel parcel) {
+                    return new GnssSignalType(parcel.readInt(), parcel.readDouble(),
+                            parcel.readString());
+                }
+
+                @Override
+                public GnssSignalType[] newArray(int i) {
+                    return new GnssSignalType[i];
+                }
+            };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mConstellationType);
+        parcel.writeDouble(mCarrierFrequencyHz);
+        parcel.writeString(mCodeType);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+        s.append("GnssSignalType[");
+        s.append(" Constellation=").append(mConstellationType);
+        s.append(", CarrierFrequencyHz=").append(mCarrierFrequencyHz);
+        s.append(", CodeType=").append(mCodeType);
+        s.append(']');
+        return s.toString();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof GnssSignalType) {
+            GnssSignalType other = (GnssSignalType) obj;
+            return mConstellationType == other.mConstellationType
+                    && Double.compare(mCarrierFrequencyHz, other.mCarrierFrequencyHz) == 0
+                    && mCodeType.equals(other.mCodeType);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConstellationType, mCarrierFrequencyHz, mCodeType);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 60e3a306..980f63b 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -23,6 +23,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.IBinder;
@@ -220,46 +221,59 @@
 
     /**
      * @hide
-     * Mute state used for anonymization.
+     * Mute state used for the initial state and when API is accessed without permission.
      */
-    public static final int PLAYER_MUTE_INVALID = -1;
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_UNKNOWN = -1;
     /**
      * @hide
      * Flag used when muted by master volume.
      */
-    public static final int PLAYER_MUTE_MASTER = (1 << 0);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_MASTER = (1 << 0);
     /**
      * @hide
      * Flag used when muted by stream volume.
      */
-    public static final int PLAYER_MUTE_STREAM_VOLUME = (1 << 1);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_STREAM_VOLUME = (1 << 1);
     /**
      * @hide
      * Flag used when muted by stream mute.
      */
-    public static final int PLAYER_MUTE_STREAM_MUTED = (1 << 2);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_STREAM_MUTED = (1 << 2);
     /**
      * @hide
-     * Flag used when playback is restricted by AppOps manager with OP_PLAY_AUDIO.
+     * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
      */
-    public static final int PLAYER_MUTE_PLAYBACK_RESTRICTED = (1 << 3);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_APP_OPS = (1 << 3);
     /**
      * @hide
      * Flag used when muted by client volume.
      */
-    public static final int PLAYER_MUTE_CLIENT_VOLUME = (1 << 4);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_CLIENT_VOLUME = (1 << 4);
     /**
      * @hide
      * Flag used when muted by volume shaper.
      */
-    public static final int PLAYER_MUTE_VOLUME_SHAPER = (1 << 5);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_VOLUME_SHAPER = (1 << 5);
 
     /** @hide */
     @IntDef(
             flag = true,
-            value = {PLAYER_MUTE_MASTER, PLAYER_MUTE_STREAM_VOLUME, PLAYER_MUTE_STREAM_MUTED,
-                    PLAYER_MUTE_PLAYBACK_RESTRICTED, PLAYER_MUTE_CLIENT_VOLUME,
-                    PLAYER_MUTE_VOLUME_SHAPER})
+            value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED,
+                    MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PlayerMuteEvent {
     }
@@ -303,7 +317,7 @@
         mPlayerType = pic.mPlayerType;
         mClientUid = uid;
         mClientPid = pid;
-        mMutedState = PLAYER_MUTE_INVALID;
+        mMutedState = MUTED_BY_UNKNOWN;
         mDeviceId = PLAYER_DEVICEID_INVALID;
         mPlayerState = PLAYER_STATE_IDLE;
         mPlayerAttr = pic.mAttributes;
@@ -352,7 +366,7 @@
         anonymCopy.mPlayerAttr = builder.build();
         anonymCopy.mDeviceId = in.mDeviceId;
         // anonymized data
-        anonymCopy.mMutedState = PLAYER_MUTE_INVALID;
+        anonymCopy.mMutedState = MUTED_BY_UNKNOWN;
         anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
         anonymCopy.mClientUid = PLAYER_UPID_INVALID;
         anonymCopy.mClientPid = PLAYER_UPID_INVALID;
@@ -413,9 +427,27 @@
 
     /**
      * @hide
-     * @return the mute state as a combination of {@link PlayerMuteEvent} flags
+     * Used for determining if the current player is muted.
+     * <br>Note that if this result is true then {@link #getMutedBy} will be > 0.
+     * @return {@code true} if the player associated with this configuration has been muted (by any
+     * given MUTED_BY_* source event) or {@code false} otherwise.
      */
-    @PlayerMuteEvent public int getMutedState() {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean isMuted() {
+        return mMutedState != 0 && mMutedState != MUTED_BY_UNKNOWN;
+    }
+
+    /**
+     * @hide
+     * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags.
+     * <br>Note that if the mute state is not set the result will be {@link #MUTED_BY_UNKNOWN}. A
+     * value of 0 represents a player which is not muted.
+     * @return the mute state.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @PlayerMuteEvent public int getMutedBy() {
         return mMutedState;
     }
 
@@ -570,14 +602,14 @@
     }
 
     private boolean isMuteAffectingActiveState() {
-        if (mMutedState == PLAYER_MUTE_INVALID) {
+        if (mMutedState == MUTED_BY_UNKNOWN) {
             // mute state is not set, therefore it will not affect the active state
             return false;
         }
 
-        return (mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0
-                || (mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0
-                || (mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0;
+        return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
+                || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
+                || (mMutedState & MUTED_BY_APP_OPS) != 0;
     }
 
     /**
@@ -694,27 +726,27 @@
                 "/").append(mClientPid).append(" state:").append(
                 toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append(
                 " sessionId:").append(mSessionId).append(" mutedState:");
-        if (mMutedState == PLAYER_MUTE_INVALID) {
-            apcToString.append("invalid ");
+        if (mMutedState == MUTED_BY_UNKNOWN) {
+            apcToString.append("unknown ");
         } else if (mMutedState == 0) {
             apcToString.append("none ");
         } else {
-            if ((mMutedState & PLAYER_MUTE_MASTER) != 0) {
+            if ((mMutedState & MUTED_BY_MASTER) != 0) {
                 apcToString.append("master ");
             }
-            if ((mMutedState & PLAYER_MUTE_STREAM_VOLUME) != 0) {
+            if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) {
                 apcToString.append("streamVolume ");
             }
-            if ((mMutedState & PLAYER_MUTE_STREAM_MUTED) != 0) {
+            if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
                 apcToString.append("streamMute ");
             }
-            if ((mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) {
-                apcToString.append("playbackRestricted ");
+            if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
+                apcToString.append("appOps ");
             }
-            if ((mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0) {
+            if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
                 apcToString.append("clientVolume ");
             }
-            if ((mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0) {
+            if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
                 apcToString.append("volumeShaper ");
             }
         }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 95599bd..1183ca3 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -174,6 +174,12 @@
     jfieldID typeId;
 } gDescriptorInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
+    jmethodID setId;
+} gBufferInfo;
+
 struct fields_t {
     jmethodID postEventFromNativeID;
     jmethodID lockAndGetContextID;
@@ -460,11 +466,7 @@
         return err;
     }
 
-    ScopedLocalRef<jclass> clazz(
-            env, env->FindClass("android/media/MediaCodec$BufferInfo"));
-
-    jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
-    env->CallVoidMethod(bufferInfo, method, (jint)offset, (jint)size, timeUs, flags);
+    env->CallVoidMethod(bufferInfo, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags);
 
     return OK;
 }
@@ -1091,13 +1093,7 @@
             CHECK(msg->findInt64("timeUs", &timeUs));
             CHECK(msg->findInt32("flags", (int32_t *)&flags));
 
-            ScopedLocalRef<jclass> clazz(
-                    env, env->FindClass("android/media/MediaCodec$BufferInfo"));
-            jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()V");
-            jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
-
-            obj = env->NewObject(clazz.get(), ctor);
-
+            obj = env->NewObject(gBufferInfo.clazz, gBufferInfo.ctorId);
             if (obj == NULL) {
                 if (env->ExceptionCheck()) {
                     ALOGE("Could not create MediaCodec.BufferInfo.");
@@ -1107,7 +1103,7 @@
                 return;
             }
 
-            env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags);
+            env->CallVoidMethod(obj, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags);
             break;
         }
 
@@ -3235,6 +3231,16 @@
 
     gDescriptorInfo.typeId = env->GetFieldID(clazz.get(), "mType", "I");
     CHECK(gDescriptorInfo.typeId != NULL);
+
+    clazz.reset(env->FindClass("android/media/MediaCodec$BufferInfo"));
+    CHECK(clazz.get() != NULL);
+    gBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+    gBufferInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+    CHECK(gBufferInfo.ctorId != NULL);
+
+    gBufferInfo.setId = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
+    CHECK(gBufferInfo.setId != NULL);
 }
 
 static void android_media_MediaCodec_native_setup(
diff --git a/packages/CredentialManager/res/drawable/ic_profile.xml b/packages/CredentialManager/res/drawable/ic_profile.xml
new file mode 100644
index 0000000..ae65940
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_profile.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+        android:viewportWidth="46"
+        android:viewportHeight="46"
+        android:width="46dp"
+        android:height="46dp">
+    <path
+        android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
+        android:strokeColor="#202124"
+        android:strokeAlpha="0.13"
+        android:strokeWidth="1" />
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index c6779fa..08ab1b4 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -22,4 +22,9 @@
   <string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
   <string name="more_options_title_multiple_options"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for <xliff:g id="createInfoTitle">%2$s</xliff:g></string>
   <string name="more_options_title_one_option"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g></string>
+  <string name="more_options_usage_data"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords and <xliff:g id="passkeyssNumber">%2$s</xliff:g> passkeys saved</string>
+  <string name="passkeys">passkeys</string>
+  <string name="passwords">passwords</string>
+  <string name="sign_ins">sign-ins</string>
+  <string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 01348e4..3d1fc92 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -33,8 +33,10 @@
 import android.os.Binder
 import android.os.Bundle
 import android.os.ResultReceiver
+import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.createflow.CreatePasskeyUiState
 import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ProviderInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
@@ -119,6 +121,10 @@
     val providerList = CreateFlowUtils.toProviderList(
       // Handle runtime cast error
       providerList as List<CreateCredentialProviderData>, context)
+    var hasDefault = false
+    var defaultProvider: ProviderInfo = providerList.first()
+    providerList.forEach{providerInfo ->
+      if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
     // TODO: covert from real requestInfo
     val requestDisplayInfo = RequestDisplayInfo(
       "Elisa Beckett",
@@ -127,8 +133,12 @@
       "tribank")
     return CreatePasskeyUiState(
       providers = providerList,
-      currentScreenState = CreateScreenState.PASSKEY_INTRO,
+      if (hasDefault)
+      {CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
       requestDisplayInfo,
+      if (hasDefault) {
+        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+      } else null
     )
   }
 
@@ -150,38 +160,39 @@
   // TODO: below are prototype functionalities. To be removed for productionization.
   private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
     return listOf(
-      CreateCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
+      CreateCredentialProviderData
+        .Builder("com.google/com.google.CredentialManagerService")
         .setSaveEntries(
           listOf<Entry>(
             newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 10000),
             newEntry("key1", "subkey-2", "elisa.work@google.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 11000),
           )
         )
         .setActionChips(
           listOf<Entry>(
-            newEntry("key2", "subkey-1", "Go to Settings", "",
-                     "20 passwords and 7 passkeys saved"),
-            newEntry("key2", "subkey-2", "Switch Account", "",
-                     "20 passwords and 7 passkeys saved"),
+            newEntry("key2", "subkey-1", "Go to Settings",
+              20, 7, 27, 20000),
+            newEntry("key2", "subkey-2", "Switch Account",
+              20, 7, 27, 21000),
           ),
         )
-        .setIsDefaultProvider(true)
+        .setIsDefaultProvider(false)
         .build(),
-      CreateCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
+      CreateCredentialProviderData
+        .Builder("com.dashlane/com.dashlane.CredentialManagerService")
         .setSaveEntries(
           listOf<Entry>(
             newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 30000),
             newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 31000),
           )
         ).setActionChips(
           listOf<Entry>(
             newEntry("key2", "subkey-3", "Manage Accounts",
-              "Manage your accounts in the dashlane app",
-                     "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 32000),
           ),
         ).build(),
     )
@@ -193,31 +204,30 @@
         .setCredentialEntries(
           listOf<Entry>(
             newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 10000),
             newEntry("key1", "subkey-2", "elisa.work@google.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 11000),
           )
         ).setActionChips(
           listOf<Entry>(
-            newEntry("key2", "subkey-1", "Go to Settings", "",
-              "20 passwords and 7 passkeys saved"),
-            newEntry("key2", "subkey-2", "Switch Account", "",
-              "20 passwords and 7 passkeys saved"),
+            newEntry("key2", "subkey-1", "Go to Settings",
+              20, 7, 27, 20000),
+            newEntry("key2", "subkey-2", "Switch Account",
+              20, 7, 27, 21000),
           ),
         ).build(),
       GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
         .setCredentialEntries(
           listOf<Entry>(
             newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 30000),
             newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 31000),
           )
         ).setActionChips(
           listOf<Entry>(
             newEntry("key2", "subkey-3", "Manage Accounts",
-              "Manage your accounts in the dashlane app",
-              "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 40000),
           ),
         ).build(),
     )
@@ -226,20 +236,32 @@
   private fun newEntry(
     key: String,
     subkey: String,
-    title: String,
-    subtitle: String,
-    usageData: String
+    providerDisplayName: String,
+    passwordCount: Int,
+    passkeyCount: Int,
+    totalCredentialCount: Int,
+    lastUsedTimeMillis: Long,
   ): Entry {
     val slice = Slice.Builder(
       Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
     )
-      .addText(title, null, listOf(Entry.HINT_TITLE))
-      .addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
+      .addText(
+        providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
       .addIcon(
         Icon.createWithResource(context, R.drawable.ic_passkey),
         null,
-        listOf(Entry.HINT_ICON))
-      .addText(usageData, Slice.SUBTYPE_MESSAGE, listOf(Entry.HINT_SUBTITLE))
+        listOf(Entry.HINT_CREDENTIAL_TYPE_ICON))
+      .addIcon(
+        Icon.createWithResource(context, R.drawable.ic_profile),
+        null,
+        listOf(Entry.HINT_PROFILE_ICON))
+      .addInt(
+        passwordCount, null, listOf(Entry.HINT_PASSWORD_COUNT))
+      .addInt(
+        passkeyCount, null, listOf(Entry.HINT_PASSKEY_COUNT))
+      .addInt(
+        totalCredentialCount, null, listOf(Entry.HINT_TOTAL_CREDENTIAL_COUNT))
+      .addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
       .build()
     return Entry(
       key,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index bf0dba2..3a8e975 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -23,6 +23,7 @@
 import com.android.credentialmanager.createflow.CreateOptionInfo
 import com.android.credentialmanager.getflow.CredentialOptionInfo
 import com.android.credentialmanager.getflow.ProviderInfo
+import com.android.credentialmanager.jetpack.provider.SaveEntryUi
 
 /** Utility functions for converting CredentialManager data structures to or from UI formats. */
 class GetFlowUtils {
@@ -59,8 +60,6 @@
           // TODO: remove fallbacks
           icon = credentialEntryUi.icon?.loadDrawable(context)
             ?: context.getDrawable(R.drawable.ic_passkey)!!,
-          title = credentialEntryUi.userName.toString(),
-          subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
           entryKey = it.key,
           entrySubkey = it.subkey,
           usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
@@ -82,9 +81,7 @@
           // TODO: replace to extract from the service data structure when available
           icon = context.getDrawable(R.drawable.ic_passkey)!!,
           name = it.providerFlattenedComponentName,
-          // TODO: get the service display name and icon from the component name.
           displayName = it.providerFlattenedComponentName,
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
           createOptions = toCreationOptionInfoList(it.saveEntries, context),
           isDefault = it.isDefaultProvider,
         )
@@ -100,13 +97,17 @@
 
         return@map CreateOptionInfo(
           // TODO: remove fallbacks
-          icon = saveEntryUi.icon?.loadDrawable(context)
-            ?: context.getDrawable(R.drawable.ic_passkey)!!,
-          title = saveEntryUi.title.toString(),
-          subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
           entryKey = it.key,
           entrySubkey = it.subkey,
-          usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData",
+          userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
+          credentialTypeIcon = saveEntryUi.credentialTypeIcon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_passkey)!!,
+          profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_profile)!!,
+          passwordCount = saveEntryUi.passwordCount ?: 0,
+          passkeyCount = saveEntryUi.passkeyCount ?: 0,
+          totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
+          lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
         )
       }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
deleted file mode 100644
index cd52197..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a save entry used during the create credential flow.
- *
- * TODO: move to jetpack.
- */
-class SaveEntryUi(
-  val title: CharSequence,
-  val subTitle: CharSequence?,
-  val icon: Icon?,
-  val usageData: CharSequence?,
-  // TODO: add
-) {
-  companion object {
-    fun fromSlice(slice: Slice): SaveEntryUi {
-      val items = slice.items
-
-      var title: String? = null
-      var subTitle: String? = null
-      var icon: Icon? = null
-      var usageData: String? = null
-
-      items.forEach {
-        if (it.hasHint(Entry.HINT_ICON)) {
-          icon = it.icon
-        } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) {
-          subTitle = it.text.toString()
-        } else if (it.hasHint(Entry.HINT_TITLE)) {
-          title = it.text.toString()
-        } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) {
-          usageData = it.text.toString()
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return SaveEntryUi(title!!, subTitle, icon, usageData)
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index db0f337e..d7e5ee8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -22,20 +22,27 @@
   val icon: Drawable,
   val name: String,
   val displayName: String,
-  val credentialTypeIcon: Drawable,
   val createOptions: List<CreateOptionInfo>,
   val isDefault: Boolean,
 )
 
-data class CreateOptionInfo(
-  val icon: Drawable,
-  val title: String,
-  val subtitle: String,
+open class EntryInfo (
   val entryKey: String,
   val entrySubkey: String,
-  val usageData: String
 )
 
+class CreateOptionInfo(
+  entryKey: String,
+  entrySubkey: String,
+  val userProviderDisplayName: String,
+  val credentialTypeIcon: Drawable,
+  val profileIcon: Drawable,
+  val passwordCount: Int,
+  val passkeyCount: Int,
+  val totalCredentialCount: Int,
+  val lastUsedTimeMillis: Long?,
+) : EntryInfo(entryKey, entrySubkey)
+
 data class RequestDisplayInfo(
   val userName: String,
   val displayName: String,
@@ -49,7 +56,7 @@
  */
 data class ActiveEntry (
   val activeProvider: ProviderInfo,
-  val activeCreateOptionInfo: CreateOptionInfo,
+  val activeEntryInfo: EntryInfo,
 )
 
 /** The name of the current screen. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index aeea46a..13a892f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -74,7 +74,7 @@
         CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
           requestDisplayInfo = uiState.requestDisplayInfo,
           providerInfo = uiState.activeEntry?.activeProvider!!,
-          createOptionInfo = uiState.activeEntry.activeCreateOptionInfo,
+          createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
           onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
           onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
           onCancel = viewModel::onCancel,
@@ -408,7 +408,7 @@
   ) {
     Column() {
       Icon(
-        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
         contentDescription = null,
         tint = Color.Unspecified,
         modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
@@ -436,12 +436,12 @@
         text = stringResource(
           R.string.choose_create_option_description,
           when (requestDisplayInfo.type) {
-            TYPE_PUBLIC_KEY_CREDENTIAL -> "passkeys"
-            TYPE_PASSWORD_CREDENTIAL -> "passwords"
-            else -> "sign-ins"
+            TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys)
+            TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords)
+            else -> stringResource(R.string.sign_ins)
           },
           providerInfo.displayName,
-          createOptionInfo.title),
+          createOptionInfo.userProviderDisplayName),
         style = Typography.body1,
         modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
       )
@@ -455,8 +455,11 @@
           verticalArrangement = Arrangement.spacedBy(2.dp)
         ) {
             item {
-              PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
-                onOptionSelected = onOptionSelected)
+              PrimaryCreateOptionRow(
+                requestDisplayInfo = requestDisplayInfo,
+                createOptionInfo = createOptionInfo,
+                onOptionSelected = onOptionSelected
+              )
             }
         }
       }
@@ -506,12 +509,17 @@
 @Composable
 fun PrimaryCreateOptionRow(
   requestDisplayInfo: RequestDisplayInfo,
+  createOptionInfo: CreateOptionInfo,
   onOptionSelected: () -> Unit
 ) {
   Chip(
     modifier = Modifier.fillMaxWidth(),
     onClick = onOptionSelected,
-    // TODO: Add an icon generated by provider according to requestDisplayInfo type
+    leadingIcon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+        bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        contentDescription = stringResource(R.string.createOptionInfo_icon_description))
+    },
     colors = ChipDefaults.chipColors(
       backgroundColor = Grey100,
       leadingIconContentColor = Grey100
@@ -545,7 +553,7 @@
         onClick = onOptionSelected,
         leadingIcon = {
             Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
-                bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
+                bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
                 // painter = painterResource(R.drawable.ic_passkey),
                 // TODO: add description.
                 contentDescription = "")
@@ -561,14 +569,15 @@
                 text =
                 if (providerInfo.createOptions.size > 1)
                 {stringResource(R.string.more_options_title_multiple_options,
-                  providerInfo.displayName, createOptionInfo.title)} else {
+                  providerInfo.displayName, createOptionInfo.userProviderDisplayName)} else {
                   stringResource(R.string.more_options_title_one_option,
                     providerInfo.displayName)},
                 style = Typography.h6,
                 modifier = Modifier.padding(top = 16.dp)
             )
             Text(
-                text = createOptionInfo.usageData,
+                text = stringResource(R.string.more_options_usage_data,
+                  createOptionInfo.passwordCount, createOptionInfo.passkeyCount),
                 style = Typography.body2,
                 modifier = Modifier.padding(bottom = 16.dp)
             )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 615da4e..2e9758a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -73,21 +73,6 @@
     )
   }
 
-  fun onCreateOptionSelected(entryKey: String, entrySubkey: String) {
-    Log.d(
-      "Account Selector",
-      "Option selected for creation: {key = $entryKey, subkey = $entrySubkey}"
-    )
-    CredentialManagerRepo.getInstance().onOptionSelected(
-      uiState.activeEntry?.activeProvider!!.name,
-      entryKey,
-      entrySubkey
-    )
-    dialogResult.value = DialogResult(
-      ResultState.COMPLETE,
-    )
-  }
-
   fun getProviderInfoByName(providerName: String): ProviderInfo {
     return uiState.providers.single {
       it.name.equals(providerName)
@@ -126,18 +111,18 @@
   }
 
   fun onPrimaryCreateOptionInfoSelected() {
-    var createOptionEntryKey = uiState.activeEntry?.activeCreateOptionInfo?.entryKey
-    var createOptionEntrySubkey = uiState.activeEntry?.activeCreateOptionInfo?.entrySubkey
+    val entryKey = uiState.activeEntry?.activeEntryInfo?.entryKey
+    val entrySubkey = uiState.activeEntry?.activeEntryInfo?.entrySubkey
     Log.d(
       "Account Selector",
       "Option selected for creation: " +
-              "{key = $createOptionEntryKey, subkey = $createOptionEntrySubkey}"
+              "{key = $entryKey, subkey = $entrySubkey}"
     )
-    if (createOptionEntryKey != null && createOptionEntrySubkey != null) {
+    if (entryKey != null && entrySubkey != null) {
       CredentialManagerRepo.getInstance().onOptionSelected(
         uiState.activeEntry?.activeProvider!!.name,
-        createOptionEntryKey,
-        createOptionEntrySubkey
+        entryKey,
+        entrySubkey
       )
     } else {
       TODO("Gracefully handle illegal state.")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index e3398c0..8b81083 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -184,12 +184,12 @@
   ) {
     Column() {
       Text(
-        text = credentialOptionInfo.title,
+        text = credentialOptionInfo.entryKey,
         style = Typography.h6,
         modifier = Modifier.padding(top = 16.dp)
       )
       Text(
-        text = credentialOptionInfo.subtitle,
+        text = credentialOptionInfo.entrySubkey,
         style = Typography.body2,
         modifier = Modifier.padding(bottom = 16.dp)
       )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b6ecd37..b427de6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -24,17 +24,21 @@
   val displayName: String,
   val credentialTypeIcon: Drawable,
   val credentialOptions: List<CredentialOptionInfo>,
+  // TODO: Add the authenticationOption
 )
 
-data class CredentialOptionInfo(
-  val icon: Drawable,
-  val title: String,
-  val subtitle: String,
+open class EntryInfo (
   val entryKey: String,
   val entrySubkey: String,
-  val usageData: String
 )
 
+class CredentialOptionInfo(
+  entryKey: String,
+  entrySubkey: String,
+  val icon: Drawable,
+  val usageData: String,
+) : EntryInfo(entryKey, entrySubkey)
+
 data class RequestDisplayInfo(
   val userName: String,
   val displayName: String,
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 20dd707..e04a9be 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -57,5 +57,5 @@
 }
 
 dependencies {
-    implementation(project(":spa"))
+    implementation project(":spa")
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 92f4fe4..742e271 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -24,6 +24,7 @@
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.home.HomePageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
+import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
@@ -69,6 +70,7 @@
                 CategoryPageProvider,
                 ActionButtonPageProvider,
                 ProgressBarPageProvider,
+                ChartPageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 04d0fe0..83c72c7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -29,6 +29,7 @@
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageModel
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
+import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
@@ -56,6 +57,7 @@
             CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
new file mode 100644
index 0000000..160e77b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.BarChartModel
+import com.android.settingslib.spa.widget.chart.LineChart
+import com.android.settingslib.spa.widget.chart.LineChartData
+import com.android.settingslib.spa.widget.chart.LineChartModel
+import com.android.settingslib.spa.widget.chart.PieChart
+import com.android.settingslib.spa.widget.chart.PieChartData
+import com.android.settingslib.spa.widget.chart.PieChartModel
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+import java.text.NumberFormat
+
+private enum class WeekDay(val num: Int) {
+    Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6),
+}
+private const val TITLE = "Sample Chart"
+
+object ChartPageProvider : SettingsPageProvider {
+    override val name = "Chart"
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = SettingsPage.create(name)
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("Line Chart", owner)
+                .setUiLayoutFn {
+                    Preference(object : PreferenceModel {
+                        override val title = "Line Chart"
+                    })
+                    LineChart(
+                        lineChartModel = object : LineChartModel {
+                            override val chartDataList = listOf(
+                                LineChartData(x = 0f, y = 0f),
+                                LineChartData(x = 1f, y = 0.1f),
+                                LineChartData(x = 2f, y = 0.2f),
+                                LineChartData(x = 3f, y = 0.6f),
+                                LineChartData(x = 4f, y = 0.9f),
+                                LineChartData(x = 5f, y = 1.0f),
+                                LineChartData(x = 6f, y = 0.8f),
+                            )
+                            override val xValueFormatter =
+                                IAxisValueFormatter { value, _ ->
+                                    "${WeekDay.values()[value.toInt()]}"
+                                }
+                            override val yValueFormatter =
+                                IAxisValueFormatter { value, _ ->
+                                    NumberFormat.getPercentInstance().format(value)
+                                }
+                        }
+                    )
+                }.build()
+        )
+        entryList.add(
+            SettingsEntryBuilder.create("Bar Chart", owner)
+                .setUiLayoutFn {
+                    Preference(object : PreferenceModel {
+                        override val title = "Bar Chart"
+                    })
+                    BarChart(
+                        barChartModel = object : BarChartModel {
+                            override val chartDataList = listOf(
+                                BarChartData(x = 0f, y = 12f),
+                                BarChartData(x = 1f, y = 5f),
+                                BarChartData(x = 2f, y = 21f),
+                                BarChartData(x = 3f, y = 5f),
+                                BarChartData(x = 4f, y = 10f),
+                                BarChartData(x = 5f, y = 9f),
+                                BarChartData(x = 6f, y = 1f),
+                            )
+                            override val xValueFormatter =
+                                IAxisValueFormatter { value, _ ->
+                                    "${WeekDay.values()[value.toInt()]}"
+                                }
+                            override val yValueFormatter =
+                                IAxisValueFormatter { value, _ ->
+                                    "${value.toInt()}m"
+                                }
+                            override val yAxisMaxValue = 30f
+                        }
+                    )
+                }.build()
+        )
+        entryList.add(
+            SettingsEntryBuilder.create("Pie Chart", owner)
+                .setUiLayoutFn {
+                    Preference(object : PreferenceModel {
+                        override val title = "Pie Chart"
+                    })
+                    PieChart(
+                        pieChartModel = object : PieChartModel {
+                            override val chartDataList = listOf(
+                                PieChartData(label = "Settings", value = 20f),
+                                PieChartData(label = "Chrome", value = 5f),
+                                PieChartData(label = "Gmail", value = 3f),
+                            )
+                            override val centerText = "Today"
+                        }
+                    )
+                }.build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+            .setIsAllowSearch(true)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(title = TITLE) {
+            for (entry in buildEntry(arguments)) {
+                entry.UiLayout(arguments)
+            }
+        }
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ChartPagePreview() {
+    SettingsTheme {
+        ChartPageProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index 897fa67..cef79c1 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -26,6 +26,7 @@
     repositories {
         google()
         mavenCentral()
+        maven { url "https://jitpack.io"}
     }
 }
 rootProject.name = "SpaLib"
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 8b29366..037b45e 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -33,6 +33,7 @@
         "androidx.navigation_navigation-compose",
         "com.google.android.material_material",
         "lottie_compose",
+        "MPAndroidChart",
     ],
     kotlincflags: [
         "-Xjvm-default=all",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 3b159e9..7a20c747 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -60,7 +60,8 @@
     api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
     api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
     api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
-    api "androidx.navigation:navigation-compose:2.5.0"
+    api "androidx.navigation:navigation-compose:2.6.0-alpha03"
+    api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
     api "com.google.android.material:material:1.7.0-alpha03"
     debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
     implementation "com.airbnb.android:lottie-compose:5.2.0"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index e63e4c9..14b1629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -30,10 +30,14 @@
     val injectEntry: SettingsEntry,
 )
 
+private fun SettingsPage.getTitle(sppRepository: SettingsPageProviderRepository): String {
+    return sppRepository.getProviderOrNull(sppName)!!.getTitle(arguments)
+}
+
 /**
  * The repository to maintain all Settings entries
  */
-class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
+class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRepository) {
     // Map of entry unique Id to entry
     private val entryMap: Map<String, SettingsEntry>
 
@@ -118,8 +122,9 @@
         return entryPath.map {
             if (it.toPage == null)
                 defaultTitle
-            else
-                it.toPage.getTitle()!!
+            else {
+                it.toPage.getTitle(sppRepository)
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 2fa9229..bb287d1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -95,11 +95,6 @@
         return false
     }
 
-    fun getTitle(): String? {
-        val sppRepository by SpaEnvironmentFactory.instance.pageProviderRepository
-        return sppRepository.getProviderOrNull(sppName)?.getTitle(arguments)
-    }
-
     fun enterPage() {
         SpaEnvironmentFactory.instance.logger.event(
             id,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
index 8e8805a..9479228 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
@@ -21,45 +21,51 @@
 import android.annotation.SuppressLint
 import android.content.Context
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.font.DeviceFontFamilyName
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
+import com.android.settingslib.spa.framework.compose.rememberContext
 
 internal data class SettingsFontFamily(
-    val brand: FontFamily = FontFamily.Default,
-    val plain: FontFamily = FontFamily.Default,
+    val brand: FontFamily,
+    val plain: FontFamily,
 )
 
-private fun Context.getSettingsFontFamily(inInspection: Boolean): SettingsFontFamily {
-    if (inInspection) {
-        return SettingsFontFamily()
-    }
+private fun Context.getSettingsFontFamily(): SettingsFontFamily {
     return SettingsFontFamily(
-        brand = FontFamily(
-            Font(getFontFamilyName("config_headlineFontFamily"), FontWeight.Normal),
-            Font(getFontFamilyName("config_headlineFontFamilyMedium"), FontWeight.Medium),
+        brand = getFontFamily(
+            configFontFamilyNormal = "config_headlineFontFamily",
+            configFontFamilyMedium = "config_headlineFontFamilyMedium",
         ),
-        plain = FontFamily(
-            Font(getFontFamilyName("config_bodyFontFamily"), FontWeight.Normal),
-            Font(getFontFamilyName("config_bodyFontFamilyMedium"), FontWeight.Medium),
+        plain = getFontFamily(
+            configFontFamilyNormal = "config_bodyFontFamily",
+            configFontFamilyMedium = "config_bodyFontFamilyMedium",
         ),
     )
 }
 
-private fun Context.getFontFamilyName(configName: String): DeviceFontFamilyName {
+private fun Context.getFontFamily(
+    configFontFamilyNormal: String,
+    configFontFamilyMedium: String,
+): FontFamily {
+    val fontFamilyNormal = getAndroidConfig(configFontFamilyNormal)
+    val fontFamilyMedium = getAndroidConfig(configFontFamilyMedium)
+    if (fontFamilyNormal.isEmpty() || fontFamilyMedium.isEmpty()) return FontFamily.Default
+    return FontFamily(
+        Font(DeviceFontFamilyName(fontFamilyNormal), FontWeight.Normal),
+        Font(DeviceFontFamilyName(fontFamilyMedium), FontWeight.Medium),
+    )
+}
+
+private fun Context.getAndroidConfig(configName: String): String {
     @SuppressLint("DiscouragedApi")
     val configId = resources.getIdentifier(configName, "string", "android")
-    return DeviceFontFamilyName(resources.getString(configId))
+    return resources.getString(configId)
 }
 
 @Composable
 internal fun rememberSettingsFontFamily(): SettingsFontFamily {
-    val context = LocalContext.current
-    val inInspection = LocalInspectionMode.current
-    return remember { context.getSettingsFontFamily(inInspection) }
+    return rememberContext(Context::getSettingsFontFamily)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
new file mode 100644
index 0000000..0b0f07e
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.settingslib.spa.framework.theme.divider
+import com.github.mikephil.charting.charts.BarChart
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.BarData
+import com.github.mikephil.charting.data.BarDataSet
+import com.github.mikephil.charting.data.BarEntry
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+
+/**
+ * The chart settings model for [BarChart].
+ */
+interface BarChartModel {
+    /**
+     * The chart data list for [BarChart].
+     */
+    val chartDataList: List<BarChartData>
+
+    /**
+     * The label text formatter for x value.
+     */
+    val xValueFormatter: IAxisValueFormatter?
+        get() = null
+
+    /**
+     * The label text formatter for y value.
+     */
+    val yValueFormatter: IAxisValueFormatter?
+        get() = null
+
+    /**
+     * The minimum value for y-axis.
+     */
+    val yAxisMinValue: Float
+        get() = 0f
+
+    /**
+     * The maximum value for y-axis.
+     */
+    val yAxisMaxValue: Float
+        get() = 1f
+
+    /**
+     * The label count for y-axis.
+     */
+    val yAxisLabelCount: Int
+        get() = 3
+}
+
+data class BarChartData(
+    var x: Float?,
+    var y: Float?,
+)
+
+@Composable
+fun BarChart(barChartModel: BarChartModel) {
+    BarChart(
+        chartDataList = barChartModel.chartDataList,
+        xValueFormatter = barChartModel.xValueFormatter,
+        yValueFormatter = barChartModel.yValueFormatter,
+        yAxisMinValue = barChartModel.yAxisMinValue,
+        yAxisMaxValue = barChartModel.yAxisMaxValue,
+        yAxisLabelCount = barChartModel.yAxisLabelCount,
+    )
+}
+
+@Composable
+fun BarChart(
+    chartDataList: List<BarChartData>,
+    modifier: Modifier = Modifier,
+    xValueFormatter: IAxisValueFormatter? = null,
+    yValueFormatter: IAxisValueFormatter? = null,
+    yAxisMinValue: Float = 0f,
+    yAxisMaxValue: Float = 30f,
+    yAxisLabelCount: Int = 4,
+) {
+    Column(
+        modifier = modifier
+            .fillMaxWidth()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        Column(
+            modifier = Modifier
+                .padding(16.dp)
+                .height(170.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center
+        ) {
+            val colorScheme = MaterialTheme.colorScheme
+            val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+            val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+            Crossfade(targetState = chartDataList) { barChartData ->
+                AndroidView(factory = { context ->
+                    BarChart(context).apply {
+                        // Fixed Settings.
+                        layoutParams = LinearLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                        )
+                        this.description.isEnabled = false
+                        this.legend.isEnabled = false
+                        this.extraBottomOffset = 4f
+                        this.setScaleEnabled(false)
+
+                        this.xAxis.position = XAxis.XAxisPosition.BOTTOM
+                        this.xAxis.setDrawGridLines(false)
+                        this.xAxis.setDrawAxisLine(false)
+                        this.xAxis.textColor = labelTextColor
+                        this.xAxis.textSize = labelTextSize
+                        this.xAxis.yOffset = 10f
+
+                        this.axisLeft.isEnabled = false
+                        this.axisRight.setDrawAxisLine(false)
+                        this.axisRight.textSize = labelTextSize
+                        this.axisRight.textColor = labelTextColor
+                        this.axisRight.gridColor = colorScheme.divider.toArgb()
+                        this.axisRight.xOffset = 10f
+
+                        // Customizable Settings.
+                        this.xAxis.valueFormatter = xValueFormatter
+                        this.axisRight.valueFormatter = yValueFormatter
+
+                        this.axisLeft.axisMinimum = yAxisMinValue
+                        this.axisLeft.axisMaximum = yAxisMaxValue
+                        this.axisRight.axisMinimum = yAxisMinValue
+                        this.axisRight.axisMaximum = yAxisMaxValue
+
+                        this.axisRight.setLabelCount(yAxisLabelCount, true)
+                    }
+                },
+                    modifier = Modifier
+                        .wrapContentSize()
+                        .padding(4.dp),
+                    update = {
+                        updateBarChartWithData(it, barChartData, colorScheme)
+                    }
+                )
+            }
+        }
+    }
+}
+
+fun updateBarChartWithData(
+    chart: BarChart,
+    data: List<BarChartData>,
+    colorScheme: ColorScheme
+) {
+    val entries = ArrayList<BarEntry>()
+    for (i in data.indices) {
+        val item = data[i]
+        entries.add(BarEntry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat()))
+    }
+
+    val ds = BarDataSet(entries, "")
+    ds.colors = arrayListOf(colorScheme.surfaceVariant.toArgb())
+    ds.setDrawValues(false)
+    ds.isHighlightEnabled = true
+    ds.highLightColor = colorScheme.primary.toArgb()
+    ds.highLightAlpha = 255
+    // TODO: Sets round corners for bars.
+
+    val d = BarData(ds)
+    chart.data = d
+    chart.invalidate()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt
new file mode 100644
index 0000000..70bc017
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.chart
+
+import androidx.compose.ui.graphics.Color
+
+object ColorPalette {
+    // Alpha = 1
+    val red: Color = Color(0xffd93025)
+    val orange: Color = Color(0xffe8710a)
+    val yellow: Color = Color(0xfff9ab00)
+    val green: Color = Color(0xff1e8e3e)
+    val cyan: Color = Color(0xff12b5cb)
+    val blue: Color = Color(0xff1a73e8)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt
new file mode 100644
index 0000000..7d48265
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.settingslib.spa.framework.theme.divider
+import com.github.mikephil.charting.charts.LineChart
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.Entry
+import com.github.mikephil.charting.data.LineData
+import com.github.mikephil.charting.data.LineDataSet
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+
+/**
+ * The chart settings model for [LineChart].
+ */
+interface LineChartModel {
+    /**
+     * The chart data list for [LineChart].
+     */
+    val chartDataList: List<LineChartData>
+
+    /**
+     * The label text formatter for x value.
+     */
+    val xValueFormatter: IAxisValueFormatter?
+        get() = null
+
+    /**
+     * The label text formatter for y value.
+     */
+    val yValueFormatter: IAxisValueFormatter?
+        get() = null
+
+    /**
+     * The minimum value for y-axis.
+     */
+    val yAxisMinValue: Float
+        get() = 0f
+
+    /**
+     * The maximum value for y-axis.
+     */
+    val yAxisMaxValue: Float
+        get() = 1f
+
+    /**
+     * The label count for y-axis.
+     */
+    val yAxisLabelCount: Int
+        get() = 3
+
+    /**
+     * Indicates whether to smooth the line.
+     */
+    val showSmoothLine: Boolean
+        get() = true
+}
+
+data class LineChartData(
+    var x: Float?,
+    var y: Float?,
+)
+
+@Composable
+fun LineChart(lineChartModel: LineChartModel) {
+    LineChart(
+        chartDataList = lineChartModel.chartDataList,
+        xValueFormatter = lineChartModel.xValueFormatter,
+        yValueFormatter = lineChartModel.yValueFormatter,
+        yAxisMinValue = lineChartModel.yAxisMinValue,
+        yAxisMaxValue = lineChartModel.yAxisMaxValue,
+        yAxisLabelCount = lineChartModel.yAxisLabelCount,
+        showSmoothLine = lineChartModel.showSmoothLine,
+    )
+}
+
+@Composable
+fun LineChart(
+    chartDataList: List<LineChartData>,
+    modifier: Modifier = Modifier,
+    xValueFormatter: IAxisValueFormatter? = null,
+    yValueFormatter: IAxisValueFormatter? = null,
+    yAxisMinValue: Float = 0f,
+    yAxisMaxValue: Float = 1f,
+    yAxisLabelCount: Int = 3,
+    showSmoothLine: Boolean = true,
+) {
+    Column(
+        modifier = modifier
+            .fillMaxWidth()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        Column(
+            modifier = Modifier
+                .padding(16.dp)
+                .height(170.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center
+        ) {
+            val colorScheme = MaterialTheme.colorScheme
+            val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+            val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+            Crossfade(targetState = chartDataList) { lineChartData ->
+                AndroidView(factory = { context ->
+                    LineChart(context).apply {
+                        // Fixed Settings.
+                        layoutParams = LinearLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                        )
+                        this.description.isEnabled = false
+                        this.legend.isEnabled = false
+                        this.extraBottomOffset = 4f
+                        this.setTouchEnabled(false)
+
+                        this.xAxis.position = XAxis.XAxisPosition.BOTTOM
+                        this.xAxis.setDrawGridLines(false)
+                        this.xAxis.setDrawAxisLine(false)
+                        this.xAxis.textColor = labelTextColor
+                        this.xAxis.textSize = labelTextSize
+                        this.xAxis.yOffset = 10f
+
+                        this.axisLeft.isEnabled = false
+
+                        this.axisRight.setDrawAxisLine(false)
+                        this.axisRight.textSize = labelTextSize
+                        this.axisRight.textColor = labelTextColor
+                        this.axisRight.gridColor = colorScheme.divider.toArgb()
+                        this.axisRight.xOffset = 10f
+                        this.axisRight.isGranularityEnabled = true
+
+                        // Customizable Settings.
+                        this.xAxis.valueFormatter = xValueFormatter
+                        this.axisRight.valueFormatter = yValueFormatter
+
+                        this.axisLeft.axisMinimum =
+                            yAxisMinValue - 0.01f * (yAxisMaxValue - yAxisMinValue)
+                        this.axisRight.axisMinimum =
+                            yAxisMinValue - 0.01f * (yAxisMaxValue - yAxisMinValue)
+                        this.axisRight.granularity =
+                            (yAxisMaxValue - yAxisMinValue) / (yAxisLabelCount - 1)
+                    }
+                },
+                    modifier = Modifier
+                        .wrapContentSize()
+                        .padding(4.dp),
+                    update = {
+                        updateLineChartWithData(it, lineChartData, colorScheme, showSmoothLine)
+                    })
+            }
+        }
+    }
+}
+
+fun updateLineChartWithData(
+    chart: LineChart,
+    data: List<LineChartData>,
+    colorScheme: ColorScheme,
+    showSmoothLine: Boolean
+) {
+    val entries = ArrayList<Entry>()
+    for (i in data.indices) {
+        val item = data[i]
+        entries.add(Entry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat()))
+    }
+
+    val ds = LineDataSet(entries, "")
+    ds.colors = arrayListOf(colorScheme.primary.toArgb())
+    ds.lineWidth = 2f
+    if (showSmoothLine) {
+        ds.mode = LineDataSet.Mode.CUBIC_BEZIER
+    }
+    ds.setDrawValues(false)
+    ds.setDrawCircles(false)
+    ds.setDrawFilled(true)
+    ds.fillColor = colorScheme.primary.toArgb()
+    ds.fillAlpha = 38
+    // TODO: enable gradient fill color for line chart.
+
+    val d = LineData(ds)
+    chart.data = d
+    chart.invalidate()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt
new file mode 100644
index 0000000..51a8d0d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.github.mikephil.charting.charts.PieChart
+import com.github.mikephil.charting.data.PieData
+import com.github.mikephil.charting.data.PieDataSet
+import com.github.mikephil.charting.data.PieEntry
+
+/**
+ * The chart settings model for [PieChart].
+ */
+interface PieChartModel {
+    /**
+     * The chart data list for [PieChart].
+     */
+    val chartDataList: List<PieChartData>
+
+    /**
+     * The center text in the hole of [PieChart].
+     */
+    val centerText: String?
+        get() = null
+}
+
+val colorPalette = arrayListOf(
+    ColorPalette.blue.toArgb(),
+    ColorPalette.red.toArgb(),
+    ColorPalette.yellow.toArgb(),
+    ColorPalette.green.toArgb(),
+    ColorPalette.orange.toArgb(),
+    ColorPalette.cyan.toArgb(),
+    Color.Blue.toArgb()
+)
+
+data class PieChartData(
+    var value: Float?,
+    var label: String?,
+)
+
+@Composable
+fun PieChart(pieChartModel: PieChartModel) {
+    PieChart(
+        chartDataList = pieChartModel.chartDataList,
+        centerText = pieChartModel.centerText,
+    )
+}
+
+@Composable
+fun PieChart(
+    chartDataList: List<PieChartData>,
+    modifier: Modifier = Modifier,
+    centerText: String? = null,
+) {
+    Column(
+        modifier = modifier
+            .fillMaxWidth()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        Column(
+            modifier = Modifier
+                .padding(16.dp)
+                .height(280.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center
+        ) {
+            val colorScheme = MaterialTheme.colorScheme
+            val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+            val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+            val centerTextSize = MaterialTheme.typography.titleLarge.fontSize.value
+            Crossfade(targetState = chartDataList) { pieChartData ->
+                AndroidView(factory = { context ->
+                    PieChart(context).apply {
+                        // Fixed settings.`
+                        layoutParams = LinearLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                        )
+                        this.isRotationEnabled = false
+                        this.description.isEnabled = false
+                        this.legend.isEnabled = false
+                        this.setTouchEnabled(false)
+
+                        this.isDrawHoleEnabled = true
+                        this.holeRadius = 90.0f
+                        this.setHoleColor(Color.Transparent.toArgb())
+                        this.setEntryLabelColor(labelTextColor)
+                        this.setEntryLabelTextSize(labelTextSize)
+                        this.setCenterTextSize(centerTextSize)
+                        this.setCenterTextColor(colorScheme.onSurface.toArgb())
+
+                        // Customizable settings.
+                        this.centerText = centerText
+                    }
+                },
+                    modifier = Modifier
+                        .wrapContentSize()
+                        .padding(4.dp), update = {
+                        updatePieChartWithData(it, pieChartData)
+                    })
+            }
+        }
+    }
+}
+
+fun updatePieChartWithData(
+    chart: PieChart,
+    data: List<PieChartData>,
+) {
+    val entries = ArrayList<PieEntry>()
+    for (i in data.indices) {
+        val item = data[i]
+        entries.add(PieEntry(item.value ?: 0.toFloat(), item.label ?: ""))
+    }
+
+    val ds = PieDataSet(entries, "")
+    ds.setDrawValues(false)
+    ds.colors = colorPalette
+    ds.sliceSpace = 2f
+    ds.yValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE
+    ds.xValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE
+    ds.valueLineColor = Color.Transparent.toArgb()
+    ds.valueLinePart1Length = 0.1f
+    ds.valueLinePart2Length = 0f
+
+    val d = PieData(ds)
+    chart.data = d
+    chart.invalidate()
+}
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index 7491045..2449dec 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -31,6 +31,7 @@
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
+        "mockito-target-minus-junit4",
         "truth-prebuilt",
     ],
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 4b4c6a3..5261091 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -58,9 +58,10 @@
 }
 
 dependencies {
-    androidTestImplementation(project(":spa"))
-    androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
-    androidTestImplementation 'com.google.truth:truth:1.1.3'
+    androidTestImplementation project(":spa")
+    androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3"
+    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
+    androidTestImplementation "com.google.truth:truth:1.1.3"
+    androidTestImplementation "org.mockito:mockito-android:3.4.6"
     androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
new file mode 100644
index 0000000..9419161
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryRepositoryTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+    private val entryRepository by spaEnvironment.entryRepository
+
+    @Test
+    fun testGetPageWithEntry() {
+        val pageWithEntry = entryRepository.getAllPageWithEntry()
+        assertThat(pageWithEntry.size).isEqualTo(3)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppHome"))
+                ?.entries?.size
+        ).isEqualTo(1)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppLayer1"))
+                ?.entries?.size
+        ).isEqualTo(3)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppLayer2"))
+                ?.entries?.size
+        ).isEqualTo(2)
+        assertThat(entryRepository.getPageWithEntry(getUniquePageId("SppWithParam"))).isNull()
+    }
+
+    @Test
+    fun testGetEntry() {
+        val entry = entryRepository.getAllEntries()
+        assertThat(entry.size).isEqualTo(7)
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "ROOT",
+                    SppHome.createSettingsPage(),
+                    SettingsPage.createNull(),
+                    SppHome.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer1.createSettingsPage(),
+                    SppHome.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer2.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                    SppLayer2.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
+            )
+        ).isNotNull()
+    }
+
+    @Test
+    fun testGetEntryPath() {
+        assertThat(
+            entryRepository.getEntryPathWithDisplayName(
+                getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+            )
+        ).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
+            .inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithTitle(
+                getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
+                "entryTitle"
+            )
+        ).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithDisplayName(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer1.createSettingsPage(),
+                    SppHome.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                )
+            )
+        ).containsExactly("INJECT_SppLayer1", "ROOT_SppHome").inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithTitle(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer2.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                    SppLayer2.createSettingsPage(),
+                ),
+                "defaultTitle"
+            )
+        ).containsExactly("SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
new file mode 100644
index 0000000..31d2ae4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.core.os.bundleOf
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val INJECT_ENTRY_NAME = "INJECT"
+const val ROOT_ENTRY_NAME = "ROOT"
+
+class MacroForTest(private val pageId: String, private val entryId: String) : EntryMacro {
+    @Composable
+    override fun UiLayout() {
+        val entryData = LocalEntryDataProvider.current
+        assertThat(entryData.isHighlighted).isFalse()
+        assertThat(entryData.pageId).isEqualTo(pageId)
+        assertThat(entryData.entryId).isEqualTo(entryId)
+    }
+
+    override fun getSearchData(): EntrySearchData {
+        return EntrySearchData("myTitle")
+    }
+
+    override fun getStatusData(): EntryStatusData {
+        return EntryStatusData(isDisabled = true, isSwitchOff = true)
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun testBuildBasic() {
+        val owner = SettingsPage.create("mySpp")
+        val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
+        assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+        assertThat(entry.displayName).isEqualTo("myEntry")
+        assertThat(entry.owner.sppName).isEqualTo("mySpp")
+        assertThat(entry.owner.displayName).isEqualTo("mySpp")
+        assertThat(entry.fromPage).isNull()
+        assertThat(entry.toPage).isNull()
+        assertThat(entry.isAllowSearch).isFalse()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.mutableStatus).isFalse()
+    }
+
+    @Test
+    fun testBuildWithLink() {
+        val owner = SettingsPage.create("mySpp")
+        val fromPage = SettingsPage.create("fromSpp")
+        val toPage = SettingsPage.create("toSpp")
+        val entryFrom = SettingsEntryBuilder.createLinkFrom("myEntry", owner)
+            .setLink(toPage = toPage).build()
+        assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
+        assertThat(entryFrom.displayName).isEqualTo("myEntry")
+        assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
+        assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
+
+        val entryTo = SettingsEntryBuilder.createLinkTo("myEntry", owner)
+            .setLink(fromPage = fromPage).build()
+        assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
+        assertThat(entryTo.displayName).isEqualTo("myEntry")
+        assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
+        assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
+    }
+
+    @Test
+    fun testBuildInject() {
+        val owner = SettingsPage.create("mySpp")
+        val entryInject = SettingsEntryBuilder.createInject(owner).build()
+        assertThat(entryInject.id).isEqualTo(
+            getUniqueEntryId(
+                INJECT_ENTRY_NAME,
+                owner,
+                toPage = owner
+            )
+        )
+        assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME}_mySpp")
+        assertThat(entryInject.fromPage).isNull()
+        assertThat(entryInject.toPage).isNotNull()
+    }
+
+    @Test
+    fun testBuildRoot() {
+        val owner = SettingsPage.create("mySpp")
+        val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
+        assertThat(entryInject.id).isEqualTo(
+            getUniqueEntryId(
+                ROOT_ENTRY_NAME,
+                owner,
+                toPage = owner
+            )
+        )
+        assertThat(entryInject.displayName).isEqualTo("myRootEntry")
+        assertThat(entryInject.fromPage).isNull()
+        assertThat(entryInject.toPage).isNotNull()
+    }
+
+    @Test
+    fun testSetAttributes() {
+        val owner = SettingsPage.create("mySpp")
+        val entry = SettingsEntryBuilder.create(owner, "myEntry")
+            .setDisplayName("myEntryDisplay")
+            .setIsAllowSearch(true)
+            .setIsSearchDataDynamic(false)
+            .setHasMutableStatus(true)
+            .build()
+        assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+        assertThat(entry.displayName).isEqualTo("myEntryDisplay")
+        assertThat(entry.fromPage).isNull()
+        assertThat(entry.toPage).isNull()
+        assertThat(entry.isAllowSearch).isTrue()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.mutableStatus).isTrue()
+    }
+
+    @Test
+    fun testSetMarco() {
+        val owner = SettingsPage.create("mySpp", arguments = bundleOf("param" to "v1"))
+        val entry = SettingsEntryBuilder.create(owner, "myEntry")
+            .setMacro {
+                assertThat(it?.getString("param")).isEqualTo("v1")
+                assertThat(it?.getString("rtParam")).isEqualTo("v2")
+                assertThat(it?.getString("unknown")).isNull()
+                MacroForTest(getUniquePageId("mySpp"), getUniqueEntryId("myEntry", owner))
+            }
+            .build()
+
+        val rtArguments = bundleOf("rtParam" to "v2")
+        composeTestRule.setContent { entry.UiLayout(rtArguments) }
+        val searchData = entry.getSearchData(rtArguments)
+        val statusData = entry.getStatusData(rtArguments)
+        assertThat(searchData?.title).isEqualTo("myTitle")
+        assertThat(searchData?.keyword).isEmpty()
+        assertThat(statusData?.isDisabled).isTrue()
+        assertThat(statusData?.isSwitchOff).isTrue()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
new file mode 100644
index 0000000..6c0c652
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageProviderRepositoryTest {
+    @Test
+    fun getStartPageTest() {
+        val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+        assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
+        assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
+
+        val sppRepoNull =
+            SettingsPageProviderRepository(emptyList(), listOf(SettingsPage.createNull()))
+        assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
+        assertThat(sppRepoNull.getAllRootPages()).contains(SettingsPage.createNull())
+
+        val rootPage1 = SettingsPage.create(name = "Spp1", displayName = "Spp1")
+        val rootPage2 = SettingsPage.create(name = "Spp2", displayName = "Spp2")
+        val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
+        val allRoots = sppRepo.getAllRootPages()
+        assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
+        assertThat(allRoots.size).isEqualTo(2)
+        assertThat(allRoots).contains(rootPage1)
+        assertThat(allRoots).contains(rootPage2)
+    }
+
+    @Test
+    fun getProviderTest() {
+        val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+        assertThat(sppRepoEmpty.getAllProviders()).isEmpty()
+        assertThat(sppRepoEmpty.getProviderOrNull("Spp")).isNull()
+
+        val sppRepo = SettingsPageProviderRepository(listOf(
+            object : SettingsPageProvider {
+                override val name = "Spp"
+            }
+        ), emptyList())
+        assertThat(sppRepo.getAllProviders().size).isEqualTo(1)
+        assertThat(sppRepo.getProviderOrNull("Spp")).isNotNull()
+        assertThat(sppRepo.getProviderOrNull("SppUnknown")).isNull()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
new file mode 100644
index 0000000..539e56b
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaLogger = SpaLoggerForTest()
+    private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+
+    @Test
+    fun testNullPage() {
+        val page = SettingsPage.createNull()
+        assertThat(page.id).isEqualTo(getUniquePageId("NULL"))
+        assertThat(page.sppName).isEqualTo("NULL")
+        assertThat(page.displayName).isEqualTo("NULL")
+        assertThat(page.buildRoute()).isEqualTo("NULL")
+        assertThat(page.isCreateBy("NULL")).isTrue()
+        assertThat(page.isCreateBy("Spp")).isFalse()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+    }
+
+    @Test
+    fun testRegularPage() {
+        val page = SettingsPage.create("mySpp", "SppDisplayName")
+        assertThat(page.id).isEqualTo(getUniquePageId("mySpp"))
+        assertThat(page.sppName).isEqualTo("mySpp")
+        assertThat(page.displayName).isEqualTo("SppDisplayName")
+        assertThat(page.buildRoute()).isEqualTo("mySpp")
+        assertThat(page.isCreateBy("NULL")).isFalse()
+        assertThat(page.isCreateBy("mySpp")).isTrue()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+            "-e spaActivityDestination mySpp"
+        )
+    }
+
+    @Test
+    fun testParamPage() {
+        val arguments = bundleOf(
+            "string_param" to "myStr",
+            "int_param" to 10,
+        )
+        val page = spaEnvironment.createPage("SppWithParam", arguments)
+        assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+        ), arguments))
+        assertThat(page.sppName).isEqualTo("SppWithParam")
+        assertThat(page.displayName).isEqualTo("SppWithParam")
+        assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
+        assertThat(page.isCreateBy("SppWithParam")).isTrue()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+            "-e spaActivityDestination SppWithParam/myStr/10"
+        )
+    }
+
+    @Test
+    fun testRtParamPage() {
+        val arguments = bundleOf(
+            "string_param" to "myStr",
+            "int_param" to 10,
+            "rt_param" to "rtStr",
+        )
+        val page = spaEnvironment.createPage("SppWithRtParam", arguments)
+        assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+            navArgument("rt_param") { type = NavType.StringType },
+        ), arguments))
+        assertThat(page.sppName).isEqualTo("SppWithRtParam")
+        assertThat(page.displayName).isEqualTo("SppWithRtParam")
+        assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
+        assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
+        assertThat(page.hasRuntimeParam()).isTrue()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+    }
+
+    @Test
+    fun testPageEvent() {
+        spaLogger.reset()
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val page = spaEnvironment.createPage("SppHome")
+        page.enterPage()
+        page.leavePage()
+        page.enterPage()
+        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+            .isEqualTo(2)
+        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+            .isEqualTo(1)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
new file mode 100644
index 0000000..b8731a3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.BrowseActivity
+
+class SpaLoggerForTest : SpaLogger {
+    data class MsgCountKey(val msg: String, val category: LogCategory)
+    data class EventCountKey(val id: String, val event: LogEvent, val category: LogCategory)
+
+    private val messageCount: MutableMap<MsgCountKey, Int> = mutableMapOf()
+    private val eventCount: MutableMap<EventCountKey, Int> = mutableMapOf()
+
+    override fun message(tag: String, msg: String, category: LogCategory) {
+        val key = MsgCountKey("[$tag]$msg", category)
+        messageCount[key] = messageCount.getOrDefault(key, 0) + 1
+    }
+
+    override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+        val key = EventCountKey(id, event, category)
+        eventCount[key] = eventCount.getOrDefault(key, 0) + 1
+    }
+
+    fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
+        val key = MsgCountKey("[$tag]$msg", category)
+        return messageCount.getOrDefault(key, 0)
+    }
+
+    fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
+        val key = EventCountKey(id, event, category)
+        return eventCount.getOrDefault(key, 0)
+    }
+
+    fun reset() {
+        messageCount.clear()
+        eventCount.clear()
+    }
+}
+
+class MockActivity : BrowseActivity()
+
+object SppHome : SettingsPageProvider {
+    override val name = "SppHome"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleHome"
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SppLayer1.buildInject().setLink(fromPage = owner).build(),
+        )
+    }
+}
+
+object SppLayer1 : SettingsPageProvider {
+    override val name = "SppLayer1"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleLayer1"
+    }
+
+    fun buildInject(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(this.createSettingsPage())
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "Layer1Entry1").build(),
+            SppLayer2.buildInject().setLink(fromPage = owner).build(),
+            SettingsEntryBuilder.create(owner, "Layer1Entry2").build(),
+        )
+    }
+}
+
+object SppLayer2 : SettingsPageProvider {
+    override val name = "SppLayer2"
+
+    fun buildInject(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(this.createSettingsPage())
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "Layer2Entry1").build(),
+            SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
+        )
+    }
+}
+
+class SpaEnvironmentForTest(
+    context: Context,
+    override val browseActivityClass: Class<out Activity>? = MockActivity::class.java,
+    override val logger: SpaLogger = SpaLoggerForTest()
+) : SpaEnvironment(context) {
+
+    override val pageProviderRepository = lazy {
+        SettingsPageProviderRepository(
+            listOf(
+                SppHome, SppLayer1, SppLayer2,
+                object : SettingsPageProvider {
+                    override val name = "SppWithParam"
+                    override val parameter = listOf(
+                        navArgument("string_param") { type = NavType.StringType },
+                        navArgument("int_param") { type = NavType.IntType },
+                    )
+                },
+                object : SettingsPageProvider {
+                    override val name = "SppWithRtParam"
+                    override val parameter = listOf(
+                        navArgument("string_param") { type = NavType.StringType },
+                        navArgument("int_param") { type = NavType.IntType },
+                        navArgument("rt_param") { type = NavType.StringType },
+                    )
+                },
+            ),
+            listOf(SettingsPage.create("SppHome"))
+        )
+    }
+
+    fun createPage(sppName: String, arguments: Bundle? = null): SettingsPage {
+        return pageProviderRepository.value
+            .getProviderOrNull(sppName)!!.createSettingsPage(arguments)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
new file mode 100644
index 0000000..93f9afe
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.normalize
+
+fun getUniquePageId(
+    name: String,
+    parameter: List<NamedNavArgument> = emptyList(),
+    arguments: Bundle? = null
+): String {
+    val normArguments = parameter.normalize(arguments)
+    return "$name:${normArguments?.toString()}".toHashId()
+}
+
+fun getUniquePageId(page: SettingsPage): String {
+    return getUniquePageId(page.sppName, page.parameter, page.arguments)
+}
+
+fun getUniqueEntryId(
+    name: String,
+    owner: SettingsPage,
+    fromPage: SettingsPage? = null,
+    toPage: SettingsPage? = null
+): String {
+    val ownerId = getUniquePageId(owner)
+    val fromId = if (fromPage == null) "null" else getUniquePageId(fromPage)
+    val toId = if (toPage == null) "null" else getUniquePageId(toPage)
+    return "$name:$ownerId($fromId-$toId)".toHashId()
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
new file mode 100644
index 0000000..2ff3039
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
@@ -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.settingslib.spa.framework.theme
+
+import android.content.Context
+import android.content.res.Resources
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.font.FontFamily
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class SettingsThemeTest {
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Mock
+    private lateinit var context: Context
+
+    @Mock
+    private lateinit var resources: Resources
+
+    private var nextMockResId = 1
+
+    @Before
+    fun setUp() {
+        whenever(context.resources).thenReturn(resources)
+        whenever(resources.getString(anyInt())).thenReturn("")
+    }
+
+    private fun mockAndroidConfig(configName: String, configValue: String) {
+        whenever(resources.getIdentifier(configName, "string", "android"))
+            .thenReturn(nextMockResId)
+        whenever(resources.getString(nextMockResId)).thenReturn(configValue)
+        nextMockResId++
+    }
+
+    @Test
+    fun noFontFamilyConfig_useDefaultFontFamily() {
+        val fontFamily = getFontFamily()
+
+        assertThat(fontFamily.headlineLarge.fontFamily).isSameInstanceAs(FontFamily.Default)
+        assertThat(fontFamily.bodyLarge.fontFamily).isSameInstanceAs(FontFamily.Default)
+    }
+
+    @Test
+    fun hasFontFamilyConfig_useConfiguredFontFamily() {
+        mockAndroidConfig("config_headlineFontFamily", "HeadlineNormal")
+        mockAndroidConfig("config_headlineFontFamilyMedium", "HeadlineMedium")
+        mockAndroidConfig("config_bodyFontFamily", "BodyNormal")
+        mockAndroidConfig("config_bodyFontFamilyMedium", "BodyMedium")
+
+        val fontFamily = getFontFamily()
+
+        val headlineFontFamily = fontFamily.headlineLarge.fontFamily.toString()
+        assertThat(headlineFontFamily).contains("HeadlineNormal")
+        assertThat(headlineFontFamily).contains("HeadlineMedium")
+        val bodyFontFamily = fontFamily.bodyLarge.fontFamily.toString()
+        assertThat(bodyFontFamily).contains("BodyNormal")
+        assertThat(bodyFontFamily).contains("BodyMedium")
+    }
+
+    private fun getFontFamily(): Typography {
+        lateinit var typography: Typography
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                SettingsTheme {
+                    typography = MaterialTheme.typography
+                }
+            }
+        }
+        return typography
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
index 21ff085..48ebd8d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -20,9 +20,12 @@
 import androidx.navigation.NamedNavArgument
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
+import org.junit.runner.RunWith
 
+@RunWith(AndroidJUnit4::class)
 class ParameterTest {
     @Test
     fun navRouteTest() {
@@ -88,14 +91,14 @@
             "Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
         )
 
-        val setParialParam = navArguments.normalize(
+        val setPartialParam = navArguments.normalize(
             bundleOf(
                 "string_param" to "myStr",
                 "rt_param" to "rtStr",
             )
         )
-        assertThat(setParialParam).isNotNull()
-        assertThat(setParialParam.toString()).isEqualTo(
+        assertThat(setPartialParam).isNotNull()
+        assertThat(setPartialParam.toString()).isEqualTo(
             "Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
         )
 
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
new file mode 100644
index 0000000..fa7a98a
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
@@ -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.settingslib.spa.widget
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.SemanticsPropertyKey
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.LineChart
+import com.android.settingslib.spa.widget.chart.LineChartData
+import com.android.settingslib.spa.widget.chart.PieChart
+import com.android.settingslib.spa.widget.chart.PieChartData
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ChartTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val Chart = SemanticsPropertyKey<String>("Chart")
+    private var SemanticsPropertyReceiver.chart by Chart
+    private fun hasChart(chart: String): SemanticsMatcher =
+        SemanticsMatcher.expectValue(Chart, chart)
+
+    @Test
+    fun line_chart_displayed() {
+        composeTestRule.setContent {
+            LineChart(
+                chartDataList = listOf(
+                    LineChartData(x = 0f, y = 0f),
+                    LineChartData(x = 1f, y = 0.1f),
+                    LineChartData(x = 2f, y = 0.2f),
+                    LineChartData(x = 3f, y = 0.7f),
+                    LineChartData(x = 4f, y = 0.9f),
+                    LineChartData(x = 5f, y = 1.0f),
+                    LineChartData(x = 6f, y = 0.8f),
+                ),
+                modifier = Modifier.semantics { chart = "line" }
+            )
+        }
+
+        composeTestRule.onNode(hasChart("line")).assertIsDisplayed()
+    }
+
+    @Test
+    fun bar_chart_displayed() {
+        composeTestRule.setContent {
+            BarChart(
+                chartDataList = listOf(
+                    BarChartData(x = 0f, y = 12f),
+                    BarChartData(x = 1f, y = 5f),
+                    BarChartData(x = 2f, y = 21f),
+                    BarChartData(x = 3f, y = 5f),
+                    BarChartData(x = 4f, y = 10f),
+                    BarChartData(x = 5f, y = 9f),
+                    BarChartData(x = 6f, y = 1f),
+                ),
+                yAxisMaxValue = 30f,
+                modifier = Modifier.semantics { chart = "bar" }
+            )
+        }
+
+        composeTestRule.onNode(hasChart("bar")).assertIsDisplayed()
+    }
+
+    @Test
+    fun pie_chart_displayed() {
+        composeTestRule.setContent {
+            PieChart(
+                chartDataList = listOf(
+                    PieChartData(label = "Settings", value = 20f),
+                    PieChartData(label = "Chrome", value = 5f),
+                    PieChartData(label = "Gmail", value = 3f),
+                ),
+                centerText = "Today",
+                modifier = Modifier.semantics { chart = "pie" }
+            )
+        }
+
+        composeTestRule.onNode(hasChart("pie")).assertIsDisplayed()
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 3cd8378..9d6b311 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -41,6 +41,13 @@
 import kotlinx.coroutines.Dispatchers
 
 private const val TAG = "AppList"
+private const val CONTENT_TYPE_HEADER = "header"
+
+internal data class AppListState(
+    val showSystem: State<Boolean>,
+    val option: State<Int>,
+    val searchQuery: State<String>,
+)
 
 /**
  * The template to render an App List.
@@ -49,23 +56,26 @@
  */
 @Composable
 internal fun <T : AppRecord> AppList(
-    appListConfig: AppListConfig,
+    config: AppListConfig,
     listModel: AppListModel<T>,
-    showSystem: State<Boolean>,
-    option: State<Int>,
-    searchQuery: State<String>,
+    state: AppListState,
+    header: @Composable () -> Unit,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
     bottomPadding: Dp,
+    appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
+        loadAppListData(config, listModel, state)
+    },
 ) {
-    LogCompositions(TAG, appListConfig.userId.toString())
-    val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
-    AppListWidget(appListData, listModel, appItem, bottomPadding)
+    LogCompositions(TAG, config.userId.toString())
+    val appListData = appListDataSupplier()
+    AppListWidget(appListData, listModel, header, appItem, bottomPadding)
 }
 
 @Composable
 private fun <T : AppRecord> AppListWidget(
     appListData: State<AppListData<T>?>,
     listModel: AppListModel<T>,
+    header: @Composable () -> Unit,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
     bottomPadding: Dp,
 ) {
@@ -81,6 +91,10 @@
             state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
             contentPadding = PaddingValues(bottom = bottomPadding),
         ) {
+            item(contentType = CONTENT_TYPE_HEADER) {
+                header()
+            }
+
             items(count = list.size, key = { option to list[it].record.app.packageName }) {
                 val appEntry = list[it]
                 val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
@@ -94,19 +108,17 @@
 }
 
 @Composable
-private fun <T : AppRecord> loadAppEntries(
-    appListConfig: AppListConfig,
+private fun <T : AppRecord> loadAppListData(
+    config: AppListConfig,
     listModel: AppListModel<T>,
-    showSystem: State<Boolean>,
-    option: State<Int>,
-    searchQuery: State<String>,
+    state: AppListState,
 ): State<AppListData<T>?> {
-    val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
-    viewModel.appListConfig.setIfAbsent(appListConfig)
+    val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
+    viewModel.appListConfig.setIfAbsent(config)
     viewModel.listModel.setIfAbsent(listModel)
-    viewModel.showSystem.Sync(showSystem)
-    viewModel.option.Sync(option)
-    viewModel.searchQuery.Sync(searchQuery)
+    viewModel.showSystem.Sync(state.showSystem)
+    viewModel.option.Sync(state.option)
+    viewModel.searchQuery.Sync(state.searchQuery)
 
     return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2953367..388a7d8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -37,6 +37,8 @@
 
 /**
  * The full screen template for an App List page.
+ *
+ * @param header the description header appears before all the applications.
  */
 @Composable
 fun <T : AppRecord> AppListPage(
@@ -44,6 +46,7 @@
     listModel: AppListModel<T>,
     showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
+    header: @Composable () -> Unit = {},
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
     val showSystem = rememberSaveable { mutableStateOf(false) }
@@ -59,14 +62,17 @@
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
                 AppList(
-                    appListConfig = AppListConfig(
+                    config = AppListConfig(
                         userId = userInfo.id,
                         showInstantApps = showInstantApps,
                     ),
                     listModel = listModel,
-                    showSystem = showSystem,
-                    option = selectedOption,
-                    searchQuery = searchQuery,
+                    state = AppListState(
+                        showSystem = showSystem,
+                        option = selectedOption,
+                        searchQuery = searchQuery,
+                    ),
+                    header = header,
                     appItem = appItem,
                     bottomPadding = bottomPadding,
                 )
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
new file mode 100644
index 0000000..80c4eac
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.icu.text.CollationKey
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppEntry
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
+import com.android.settingslib.spaprivileged.model.app.AppListData
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun whenNoApps() {
+        setContent(appEntries = emptyList())
+
+        composeTestRule.onNodeWithText(context.getString(R.string.no_applications))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun couldShowAppItem() {
+        setContent(appEntries = listOf(APP_ENTRY))
+
+        composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+    }
+
+    @Test
+    fun couldShowHeader() {
+        setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+
+        composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
+    }
+
+    private fun setContent(
+        header: @Composable () -> Unit = {},
+        appEntries: List<AppEntry<TestAppRecord>>,
+    ) {
+        composeTestRule.setContent {
+            AppList(
+                config = AppListConfig(userId = USER_ID, showInstantApps = false),
+                listModel = TestAppListModel(),
+                state = AppListState(
+                    showSystem = false.toState(),
+                    option = 0.toState(),
+                    searchQuery = "".toState(),
+                ),
+                header = header,
+                appItem = { AppListItem(it) {} },
+                bottomPadding = 0.dp,
+                appListDataSupplier = {
+                    stateOf(AppListData(appEntries, option = 0))
+                }
+            )
+        }
+    }
+
+    private companion object {
+        const val USER_ID = 0
+        const val HEADER = "Header"
+        val APP_ENTRY = AppEntry(
+            record = TestAppRecord(ApplicationInfo()),
+            label = "AAA",
+            labelCollationKey = CollationKey("", byteArrayOf()),
+        )
+    }
+}
+
+private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+
+private class TestAppListModel : AppListModel<TestAppRecord> {
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        appListFlow.asyncMapItem { TestAppRecord(it) }
+
+    @Composable
+    override fun getSummary(option: Int, record: TestAppRecord) = null
+
+    override fun filter(
+        userIdFlow: Flow<Int>,
+        option: Int,
+        recordListFlow: Flow<List<TestAppRecord>>,
+    ) = recordListFlow
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 6bc1160..dd56bde 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -188,7 +188,6 @@
     /**
      * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
      * Hearing Aid Service is connected and the HiSyncId's are now available.
-     * @param LocalBluetoothProfileManager profileManager
      */
     public synchronized void updateHearingAidsDevices() {
         mHearingAidDeviceManager.updateHearingAidsDevices();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
new file mode 100644
index 0000000..f06aab3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HapClientProfile handles the Bluetooth HAP service client role.
+ */
+public class HapClientProfile implements LocalBluetoothProfile {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            HearingAidType.TYPE_INVALID,
+            HearingAidType.TYPE_BINAURAL,
+            HearingAidType.TYPE_MONAURAL,
+            HearingAidType.TYPE_BANDED,
+            HearingAidType.TYPE_RFU
+    })
+
+    /** Hearing aid type definition for HAP Client. */
+    public @interface HearingAidType {
+        int TYPE_INVALID = -1;
+        int TYPE_BINAURAL = BluetoothHapClient.TYPE_BINAURAL;
+        int TYPE_MONAURAL = BluetoothHapClient.TYPE_MONAURAL;
+        int TYPE_BANDED = BluetoothHapClient.TYPE_BANDED;
+        int TYPE_RFU = BluetoothHapClient.TYPE_RFU;
+    }
+
+    static final String NAME = "HapClient";
+    private static final String TAG = "HapClientProfile";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 1;
+
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+    private BluetoothHapClient mService;
+    private boolean mIsProfileReady;
+
+    // These callbacks run on the main thread.
+    private final class HapClientServiceListener implements BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mService = (BluetoothHapClient) proxy;
+            // We just bound to the service, so refresh the UI for any connected HapClient devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // Adds a new device into mDeviceManager if it does not exist
+                if (device == null) {
+                    Log.w(TAG, "HapClient profile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(nextDevice);
+                }
+                device.onProfileStateChanged(
+                        HapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mIsProfileReady = true;
+            mProfileManager.callServiceConnectedListeners();
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            mIsProfileReady = false;
+            mProfileManager.callServiceDisconnectedListeners();
+        }
+    }
+
+    HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+        if (bluetoothManager != null) {
+            mBluetoothAdapter = bluetoothManager.getAdapter();
+            mBluetoothAdapter.getProfileProxy(context, new HapClientServiceListener(),
+                    BluetoothProfile.HAP_CLIENT);
+        } else {
+            mBluetoothAdapter = null;
+        }
+    }
+
+    /**
+     * Get hearing aid devices matching connection states{
+     * {@code BluetoothProfile.STATE_CONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTING},
+     * {@code BluetoothProfile.STATE_DISCONNECTING}}
+     *
+     * @return Matching device list
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        return getDevicesByStates(new int[] {
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    /**
+     * Get hearing aid devices matching connection states{
+     * {@code BluetoothProfile.STATE_DISCONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTING},
+     * {@code BluetoothProfile.STATE_DISCONNECTING}}
+     *
+     * @return Matching device list
+     */
+    public List<BluetoothDevice> getConnectableDevices() {
+        return getDevicesByStates(new int[] {
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    private List<BluetoothDevice> getDevicesByStates(int[] states) {
+        if (mService == null) {
+            return new ArrayList<>(0);
+        }
+        return mService.getDevicesMatchingConnectionStates(states);
+    }
+
+    /**
+     * Gets the hearing aid type of the device.
+     *
+     * @param device is the device for which we want to get the hearing aid type
+     * @return hearing aid type
+     */
+    @HearingAidType
+    public int getHearingAidType(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return HearingAidType.TYPE_INVALID;
+        }
+        return mService.getHearingAidType(device);
+    }
+
+    /**
+     * Gets if this device supports synchronized presets or not
+     *
+     * @param device is the device for which we want to know if supports synchronized presets
+     * @return {@code true} if the device supports synchronized presets
+     */
+    public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportSynchronizedPresets(device);
+    }
+
+    /**
+     * Gets if this device supports independent presets or not
+     *
+     * @param device is the device for which we want to know if supports independent presets
+     * @return {@code true} if the device supports independent presets
+     */
+    public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportIndependentPresets(device);
+    }
+
+    /**
+     * Gets if this device supports dynamic presets or not
+     *
+     * @param device is the device for which we want to know if supports dynamic presets
+     * @return {@code true} if the device supports dynamic presets
+     */
+    public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportDynamicPresets(device);
+    }
+
+    /**
+     * Gets if this device supports writable presets or not
+     *
+     * @param device is the device for which we want to know if supports writable presets
+     * @return {@code true} if the device supports writable presets
+     */
+    public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportWritablePresets(device);
+    }
+
+    @Override
+    public boolean accessProfileEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    @Override
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return false;
+        }
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return CONNECTION_POLICY_FORBIDDEN;
+        }
+        return mService.getConnectionPolicy(device);
+    }
+
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null || device == null) {
+            return false;
+        }
+        if (enabled) {
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+            }
+        } else {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+        }
+
+        return isEnabled;
+    }
+
+    @Override
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    @Override
+    public int getProfileId() {
+        return BluetoothProfile.HAP_CLIENT;
+    }
+
+    @Override
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    @Override
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_hearing_aid;
+    }
+
+    @Override
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_hearing_aid_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_hearing_aid_profile_summary_connected;
+
+            default:
+                return BluetoothUtils.getConnectionStateSummary(state);
+        }
+    }
+
+    @Override
+    public int getDrawableResource(BluetoothClass btClass) {
+        return com.android.internal.R.drawable.ic_bt_hearing_aid;
+    }
+
+    /**
+     * Gets the name of this class
+     *
+     * @return the name of this class
+     */
+    public String toString() {
+        return NAME;
+    }
+
+    protected void finalize() {
+        Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mService);
+                mService = null;
+            } catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up HAP Client proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
index 51cf59c..ac9cdac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
@@ -34,16 +34,16 @@
     private static final float CORNER_LINE_LENGTH = 264f;   // 264dp
     private static final float CORNER_RADIUS = 16f;         // 16dp
 
-    final private int mCornerColor;
-    final private int mFocusedCornerColor;
-    final private int mBackgroundColor;
+    private final int mCornerColor;
+    private final int mFocusedCornerColor;
+    private final int mBackgroundColor;
 
-    final private Paint mStrokePaint;
-    final private Paint mTransparentPaint;
-    final private Paint mBackgroundPaint;
+    private final Paint mStrokePaint;
+    private final Paint mTransparentPaint;
+    private final Paint mBackgroundPaint;
 
-    final private float mRadius;
-    final private float mInnerRidus;
+    private final float mRadius;
+    private final float mInnerRadius;
 
     private Bitmap mMaskBitmap;
     private Canvas mMaskCanvas;
@@ -72,7 +72,7 @@
         mRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS,
                 getResources().getDisplayMetrics());
         // Inner radius needs to minus stroke width for keeping the width of border consistent.
-        mInnerRidus = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+        mInnerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                 CORNER_RADIUS - CORNER_STROKE_WIDTH, getResources().getDisplayMetrics());
 
         mCornerColor = context.getResources().getColor(R.color.qr_corner_line_color);
@@ -95,7 +95,10 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        if(mMaskBitmap == null) {
+        if (!isLaidOut()) {
+            return;
+        }
+        if (mMaskBitmap == null) {
             mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
             mMaskCanvas = new Canvas(mMaskBitmap);
         }
@@ -105,16 +108,18 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        // Set frame line color.
-        mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
-        // Draw background color.
-        mMaskCanvas.drawColor(mBackgroundColor);
-        // Draw outer corner.
-        mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
-        // Draw inner transparent corner.
-        mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRidus, mInnerRidus, mTransparentPaint);
+        if (mMaskCanvas != null && mMaskBitmap != null) {
+            // Set frame line color.
+            mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
+            // Draw background color.
+            mMaskCanvas.drawColor(mBackgroundColor);
+            // Draw outer corner.
+            mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
+            // Draw inner transparent corner.
+            mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRadius, mInnerRadius, mTransparentPaint);
 
-        canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+            canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+        }
         super.onDraw(canvas);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
new file mode 100644
index 0000000..03a792a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class HapClientProfileTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private CachedBluetoothDeviceManager mDeviceManager;
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
+    @Mock
+    private BluetoothHapClient mService;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private BluetoothProfile.ServiceListener mServiceListener;
+    private HapClientProfile mProfile;
+
+    @Before
+    public void setUp() {
+        mProfile = new HapClientProfile(mContext, mDeviceManager, mProfileManager);
+        final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+        final ShadowBluetoothAdapter shadowBluetoothAdapter =
+                Shadow.extract(bluetoothManager.getAdapter());
+        mServiceListener = shadowBluetoothAdapter.getServiceListener();
+    }
+
+    @Test
+    public void onServiceConnected_isProfileReady() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        assertThat(mProfile.isProfileReady()).isTrue();
+        verify(mProfileManager).callServiceConnectedListeners();
+    }
+
+    @Test
+    public void onServiceDisconnected_isProfileNotReady() {
+        mServiceListener.onServiceDisconnected(BluetoothProfile.HAP_CLIENT);
+
+        assertThat(mProfile.isProfileReady()).isFalse();
+        verify(mProfileManager).callServiceDisconnectedListeners();
+    }
+
+    @Test
+    public void getConnectionStatus_returnCorrectConnectionState() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionState(mBluetoothDevice))
+                .thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+    }
+
+    @Test
+    public void isEnabled_connectionPolicyAllowed_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+        assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
+    }
+
+    @Test
+    public void isEnabled_connectionPolicyForbidden_returnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+
+        assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
+    }
+
+    @Test
+    public void getConnectionPolicy_returnCorrectConnectionPolicy() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+        assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
+                .isEqualTo(CONNECTION_POLICY_ALLOWED);
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+    }
+
+    @Test
+    public void getConnectedDevices_returnCorrectList() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        int[] connectedStates = new int[] {
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING};
+        List<BluetoothDevice> connectedList = Arrays.asList(
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice);
+        when(mService.getDevicesMatchingConnectionStates(connectedStates))
+                .thenReturn(connectedList);
+
+        assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
+    }
+
+    @Test
+    public void getConnectableDevices_returnCorrectList() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        int[] connectableStates = new int[] {
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING};
+        List<BluetoothDevice> connectableList = Arrays.asList(
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice);
+        when(mService.getDevicesMatchingConnectionStates(connectableStates))
+                .thenReturn(connectableList);
+
+        assertThat(mProfile.getConnectableDevices().size()).isEqualTo(connectableList.size());
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index def7ddc..98af15a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -122,6 +122,7 @@
         Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
         Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
         Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
+        Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
         Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
         Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
         Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cde4bc4..80af69c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -177,6 +177,7 @@
         VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
                 NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4267ba2..07e37d3 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -557,6 +557,16 @@
                   android:showForAllUsers="true">
         </activity>
 
+        <!-- started from SensoryPrivacyService -->
+        <activity android:name=".sensorprivacy.television.TvSensorPrivacyChangedActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
+            android:theme="@style/BottomSheet"
+            android:finishOnCloseSystemDialogs="true"
+            android:showForAllUsers="true">
+        </activity>
+
 
         <!-- started from UsbDeviceSettingsManager -->
         <activity android:name=".usb.UsbAccessoryUriActivity"
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index e30d441..8d44315 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -35,4 +35,6 @@
     <!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at
          fade_out_complete_frame -->
     <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
+
+    <integer name="qs_carrier_max_em">7</integer>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b325c56..d2deb4f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -783,6 +783,62 @@
         Microphone and camera available
     </string>
 
+    <!--- Title of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_turned_on_dialog_title">
+        Microphone turned on
+    </string>
+
+    <!--- Title of dialog triggered if the microphone sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_turned_off_dialog_title">
+        Microphone turned off
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_unblocked_dialog_content">
+        Microphone is enabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are no active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content">
+        Microphone access is disabled for all apps and services.
+        You can enable microphone access in Settings &gt; Privacy &gt; Microphone.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content">
+        Microphone access is disabled for all apps and services.
+        You can change this in Settings &gt; Privacy &gt; Microphone.
+    </string>
+
+    <!--- Title of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_turned_on_dialog_title">
+        Camera turned on
+    </string>
+
+    <!--- Title of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_turned_off_dialog_title">
+        Camera turned off
+    </string>
+
+    <!--- Content of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_unblocked_dialog_content">
+        Camera is enabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_blocked_dialog_content">
+        Camera access is disabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_htt_blocked_dialog_content">To use the microphone button, enable microphone access in Settings.</string>
+
+    <!-- Sensor privacy dialog: Button to open system settings [CHAR LIMIT=50] -->
+    <string name="sensor_privacy_dialog_open_settings">Open settings.</string>
+
     <!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
     <string name="media_seamless_other_device">Other device</string>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index e743ec8..bfbe88c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,6 +20,7 @@
 import android.graphics.Region;
 import android.os.Bundle;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
 oneway interface IOverviewProxy {
@@ -44,12 +45,6 @@
     void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8;
 
     /**
-     * Sent when there was an action on one of the onboarding tips view.
-     * TODO: Move this implementation to SystemUI completely
-     */
-    void onTip(int actionType, int viewType) = 10;
-
-    /**
      * Sent when device assistant changes its default assistant whether it is available or not.
      */
     void onAssistantAvailable(boolean available) = 13;
@@ -60,13 +55,6 @@
     void onAssistantVisibilityChanged(float visibility) = 14;
 
     /**
-     * Sent when back is triggered.
-     * TODO: Move this implementation to SystemUI completely
-     */
-    void onBackAction(boolean completed, int downX, int downY, boolean isButton,
-            boolean gestureSwipeLeft) = 15;
-
-    /**
      * Sent when some system ui state changes.
      */
     void onSystemUiStateChanged(int stateFlags) = 16;
@@ -115,4 +103,9 @@
       * Sent when split keyboard shortcut is triggered to enter stage split.
       */
      void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+
+     /**
+      * Sent when the surface for navigation bar is created or changed
+      */
+     void onNavigationBarSurface(in SurfaceControl surface) = 26;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2b2b05ce..1c532fe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -24,7 +24,6 @@
 import android.view.MotionEvent;
 
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.RemoteTransitionCompat;
 
 /**
  * Temporary callbacks into SystemUI.
@@ -106,9 +105,6 @@
     /** Sets home rotation enabled. */
     void setHomeRotationEnabled(boolean enabled) = 45;
 
-    /** Notifies that a swipe-up gesture has started */
-    oneway void notifySwipeUpGestureStarted() = 46;
-
     /** Notifies when taskbar status updated */
     oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
deleted file mode 100644
index 37e706a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TransitionOldType;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-
-import android.annotation.SuppressLint;
-import android.app.IApplicationThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-
-import com.android.wm.shell.util.CounterRotator;
-
-/**
- * @see RemoteAnimationAdapter
- */
-public class RemoteAnimationAdapterCompat {
-
-    private final RemoteAnimationAdapter mWrapped;
-    private final RemoteTransitionCompat mRemoteTransition;
-
-    public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration,
-            long statusBarTransitionDelay, IApplicationThread appThread) {
-        mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration,
-                statusBarTransitionDelay);
-        mRemoteTransition = buildRemoteTransition(runner, appThread);
-    }
-
-    public RemoteAnimationAdapter getWrapped() {
-        return mWrapped;
-    }
-
-    /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */
-    public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner,
-            IApplicationThread appThread) {
-        return new RemoteTransitionCompat(
-                new RemoteTransition(wrapRemoteTransition(runner), appThread));
-    }
-
-    public RemoteTransitionCompat getRemoteTransition() {
-        return mRemoteTransition;
-    }
-
-    /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
-    public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
-            final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
-        return new IRemoteAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(@TransitionOldType int transit,
-                    RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers,
-                    RemoteAnimationTarget[] nonApps,
-                    final IRemoteAnimationFinishedCallback finishedCallback) {
-                final Runnable animationFinishedCallback = new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            finishedCallback.onAnimationFinished();
-                        } catch (RemoteException e) {
-                            Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
-                                    + " finished callback", e);
-                        }
-                    }
-                };
-                remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers,
-                        nonApps, animationFinishedCallback);
-            }
-
-            @Override
-            public void onAnimationCancelled(boolean isKeyguardOccluded) {
-                remoteAnimationAdapter.onAnimationCancelled();
-            }
-        };
-    }
-
-    private static IRemoteTransition.Stub wrapRemoteTransition(
-            final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
-        return new IRemoteTransition.Stub() {
-            final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
-
-            @Override
-            public void startAnimation(IBinder token, TransitionInfo info,
-                    SurfaceControl.Transaction t,
-                    IRemoteTransitionFinishedCallback finishCallback) {
-                final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
-                final RemoteAnimationTarget[] apps =
-                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
-                final RemoteAnimationTarget[] wallpapers =
-                        RemoteAnimationTargetCompat.wrapNonApps(
-                                info, true /* wallpapers */, t, leashMap);
-                final RemoteAnimationTarget[] nonApps =
-                        RemoteAnimationTargetCompat.wrapNonApps(
-                                info, false /* wallpapers */, t, leashMap);
-
-                // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
-                boolean isReturnToHome = false;
-                TransitionInfo.Change launcherTask = null;
-                TransitionInfo.Change wallpaper = null;
-                int launcherLayer = 0;
-                int rotateDelta = 0;
-                float displayW = 0;
-                float displayH = 0;
-                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                    final TransitionInfo.Change change = info.getChanges().get(i);
-                    // skip changes that we didn't wrap
-                    if (!leashMap.containsKey(change.getLeash())) continue;
-                    if (change.getTaskInfo() != null
-                            && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
-                        isReturnToHome = change.getMode() == TRANSIT_OPEN
-                                || change.getMode() == TRANSIT_TO_FRONT;
-                        launcherTask = change;
-                        launcherLayer = info.getChanges().size() - i;
-                    } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
-                        wallpaper = change;
-                    }
-                    if (change.getParent() == null && change.getEndRotation() >= 0
-                            && change.getEndRotation() != change.getStartRotation()) {
-                        rotateDelta = change.getEndRotation() - change.getStartRotation();
-                        displayW = change.getEndAbsBounds().width();
-                        displayH = change.getEndAbsBounds().height();
-                    }
-                }
-
-                // Prepare for rotation if there is one
-                final CounterRotator counterLauncher = new CounterRotator();
-                final CounterRotator counterWallpaper = new CounterRotator();
-                if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
-                    counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
-                            rotateDelta, displayW, displayH);
-                    if (counterLauncher.getSurface() != null) {
-                        t.setLayer(counterLauncher.getSurface(), launcherLayer);
-                    }
-                }
-
-                if (isReturnToHome) {
-                    if (counterLauncher.getSurface() != null) {
-                        t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
-                    }
-                    // Need to "boost" the closing things since that's what launcher expects.
-                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                        final TransitionInfo.Change change = info.getChanges().get(i);
-                        final SurfaceControl leash = leashMap.get(change.getLeash());
-                        // skip changes that we didn't wrap
-                        if (leash == null) continue;
-                        final int mode = info.getChanges().get(i).getMode();
-                        // Only deal with independent layers
-                        if (!TransitionInfo.isIndependent(change, info)) continue;
-                        if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
-                            t.setLayer(leash, info.getChanges().size() * 3 - i);
-                            counterLauncher.addChild(t, leash);
-                        }
-                    }
-                    // Make wallpaper visible immediately since launcher apparently won't do this.
-                    for (int i = wallpapers.length - 1; i >= 0; --i) {
-                        t.show(wallpapers[i].leash);
-                        t.setAlpha(wallpapers[i].leash, 1.f);
-                    }
-                } else {
-                    if (launcherTask != null) {
-                        counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
-                    }
-                    if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
-                        counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
-                                rotateDelta, displayW, displayH);
-                        if (counterWallpaper.getSurface() != null) {
-                            t.setLayer(counterWallpaper.getSurface(), -1);
-                            counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
-                        }
-                    }
-                }
-                t.apply();
-
-                final Runnable animationFinishedCallback = new Runnable() {
-                    @Override
-                    @SuppressLint("NewApi")
-                    public void run() {
-                        final SurfaceControl.Transaction finishTransaction =
-                                new SurfaceControl.Transaction();
-                        counterLauncher.cleanUp(finishTransaction);
-                        counterWallpaper.cleanUp(finishTransaction);
-                        // Release surface references now. This is apparently to free GPU memory
-                        // while doing quick operations (eg. during CTS).
-                        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                            info.getChanges().get(i).getLeash().release();
-                        }
-                        // Don't release here since launcher might still be using them. Instead
-                        // let launcher release them (eg. via RemoteAnimationTargets)
-                        leashMap.clear();
-                        try {
-                            finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
-                        } catch (RemoteException e) {
-                            Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
-                                    + " finished callback", e);
-                        }
-                    }
-                };
-                synchronized (mFinishRunnables) {
-                    mFinishRunnables.put(token, animationFinishedCallback);
-                }
-                // TODO(bc-unlcok): Pass correct transit type.
-                remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
-                        apps, wallpapers, nonApps, () -> {
-                            synchronized (mFinishRunnables) {
-                                if (mFinishRunnables.remove(token) == null) return;
-                            }
-                            animationFinishedCallback.run();
-                        });
-            }
-
-            @Override
-            public void mergeAnimation(IBinder token, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishCallback) {
-                // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
-                //       to legacy cancel.
-                final Runnable finishRunnable;
-                synchronized (mFinishRunnables) {
-                    finishRunnable = mFinishRunnables.remove(mergeTarget);
-                }
-                if (finishRunnable == null) return;
-                remoteAnimationAdapter.onAnimationCancelled();
-                finishRunnable.run();
-            }
-        };
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
deleted file mode 100644
index ab55037..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.view.RemoteAnimationDefinition;
-
-/**
- * @see RemoteAnimationDefinition
- */
-public class RemoteAnimationDefinitionCompat {
-
-    private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition();
-
-    public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) {
-        mWrapped.addRemoteAnimation(transition, adapter.getWrapped());
-    }
-
-    public void addRemoteAnimation(int transition, int activityTypeFilter,
-            RemoteAnimationAdapterCompat adapter) {
-        mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped());
-    }
-
-    public RemoteAnimationDefinition getWrapped() {
-        return mWrapped;
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5809c81..93c8073 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,12 +16,197 @@
 
 package com.android.systemui.shared.system;
 
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
-public interface RemoteAnimationRunnerCompat {
-    void onAnimationStart(@WindowManager.TransitionOldType int transit,
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManager.TransitionOldType;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.util.CounterRotator;
+
+public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
+
+    public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,
             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
-    void onAnimationCancelled();
+
+    @Override
+    public final void onAnimationStart(@TransitionOldType int transit,
+            RemoteAnimationTarget[] apps,
+            RemoteAnimationTarget[] wallpapers,
+            RemoteAnimationTarget[] nonApps,
+            final IRemoteAnimationFinishedCallback finishedCallback) {
+
+        onAnimationStart(transit, apps, wallpapers,
+                nonApps, () -> {
+                    try {
+                        finishedCallback.onAnimationFinished();
+                    } catch (RemoteException e) {
+                        Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+                                + " finished callback", e);
+                    }
+                });
+    }
+
+    public IRemoteTransition toRemoteTransition() {
+        return new IRemoteTransition.Stub() {
+            final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
+            @Override
+            public void startAnimation(IBinder token, TransitionInfo info,
+                    SurfaceControl.Transaction t,
+                    IRemoteTransitionFinishedCallback finishCallback) {
+                final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
+                final RemoteAnimationTarget[] apps =
+                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
+                final RemoteAnimationTarget[] wallpapers =
+                        RemoteAnimationTargetCompat.wrapNonApps(
+                                info, true /* wallpapers */, t, leashMap);
+                final RemoteAnimationTarget[] nonApps =
+                        RemoteAnimationTargetCompat.wrapNonApps(
+                                info, false /* wallpapers */, t, leashMap);
+
+                // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
+                boolean isReturnToHome = false;
+                TransitionInfo.Change launcherTask = null;
+                TransitionInfo.Change wallpaper = null;
+                int launcherLayer = 0;
+                int rotateDelta = 0;
+                float displayW = 0;
+                float displayH = 0;
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    final TransitionInfo.Change change = info.getChanges().get(i);
+                    // skip changes that we didn't wrap
+                    if (!leashMap.containsKey(change.getLeash())) continue;
+                    if (change.getTaskInfo() != null
+                            && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+                        isReturnToHome = change.getMode() == TRANSIT_OPEN
+                                || change.getMode() == TRANSIT_TO_FRONT;
+                        launcherTask = change;
+                        launcherLayer = info.getChanges().size() - i;
+                    } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+                        wallpaper = change;
+                    }
+                    if (change.getParent() == null && change.getEndRotation() >= 0
+                            && change.getEndRotation() != change.getStartRotation()) {
+                        rotateDelta = change.getEndRotation() - change.getStartRotation();
+                        displayW = change.getEndAbsBounds().width();
+                        displayH = change.getEndAbsBounds().height();
+                    }
+                }
+
+                // Prepare for rotation if there is one
+                final CounterRotator counterLauncher = new CounterRotator();
+                final CounterRotator counterWallpaper = new CounterRotator();
+                if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
+                    counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
+                            rotateDelta, displayW, displayH);
+                    if (counterLauncher.getSurface() != null) {
+                        t.setLayer(counterLauncher.getSurface(), launcherLayer);
+                    }
+                }
+
+                if (isReturnToHome) {
+                    if (counterLauncher.getSurface() != null) {
+                        t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
+                    }
+                    // Need to "boost" the closing things since that's what launcher expects.
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        final TransitionInfo.Change change = info.getChanges().get(i);
+                        final SurfaceControl leash = leashMap.get(change.getLeash());
+                        // skip changes that we didn't wrap
+                        if (leash == null) continue;
+                        final int mode = info.getChanges().get(i).getMode();
+                        // Only deal with independent layers
+                        if (!TransitionInfo.isIndependent(change, info)) continue;
+                        if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+                            t.setLayer(leash, info.getChanges().size() * 3 - i);
+                            counterLauncher.addChild(t, leash);
+                        }
+                    }
+                    // Make wallpaper visible immediately since launcher apparently won't do this.
+                    for (int i = wallpapers.length - 1; i >= 0; --i) {
+                        t.show(wallpapers[i].leash);
+                        t.setAlpha(wallpapers[i].leash, 1.f);
+                    }
+                } else {
+                    if (launcherTask != null) {
+                        counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
+                    }
+                    if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
+                        counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
+                                rotateDelta, displayW, displayH);
+                        if (counterWallpaper.getSurface() != null) {
+                            t.setLayer(counterWallpaper.getSurface(), -1);
+                            counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
+                        }
+                    }
+                }
+                t.apply();
+
+                final Runnable animationFinishedCallback = () -> {
+                    final SurfaceControl.Transaction finishTransaction =
+                            new SurfaceControl.Transaction();
+                    counterLauncher.cleanUp(finishTransaction);
+                    counterWallpaper.cleanUp(finishTransaction);
+                    // Release surface references now. This is apparently to free GPU memory
+                    // while doing quick operations (eg. during CTS).
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        info.getChanges().get(i).getLeash().release();
+                    }
+                    // Don't release here since launcher might still be using them. Instead
+                    // let launcher release them (eg. via RemoteAnimationTargets)
+                    leashMap.clear();
+                    try {
+                        finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+                    } catch (RemoteException e) {
+                        Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+                                + " finished callback", e);
+                    }
+                };
+                synchronized (mFinishRunnables) {
+                    mFinishRunnables.put(token, animationFinishedCallback);
+                }
+                // TODO(bc-unlcok): Pass correct transit type.
+                onAnimationStart(TRANSIT_OLD_NONE,
+                        apps, wallpapers, nonApps, () -> {
+                            synchronized (mFinishRunnables) {
+                                if (mFinishRunnables.remove(token) == null) return;
+                            }
+                            animationFinishedCallback.run();
+                        });
+            }
+
+            @Override
+            public void mergeAnimation(IBinder token, TransitionInfo info,
+                    SurfaceControl.Transaction t, IBinder mergeTarget,
+                    IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+                // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+                //       to legacy cancel.
+                final Runnable finishRunnable;
+                synchronized (mFinishRunnables) {
+                    finishRunnable = mFinishRunnables.remove(mergeTarget);
+                }
+                if (finishRunnable == null) return;
+                onAnimationCancelled(false /* isKeyguardOccluded */);
+                finishRunnable.run();
+            }
+        };
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d6655a7..d4d3d25 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -18,29 +18,22 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
 
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IApplicationThread;
-import android.content.ComponentName;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -53,72 +46,23 @@
 import android.window.PictureInPictureSurfaceTransaction;
 import android.window.RemoteTransition;
 import android.window.TaskSnapshot;
-import android.window.TransitionFilter;
 import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DataClass;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.util.ArrayList;
-import java.util.concurrent.Executor;
 
 /**
- * Wrapper to expose RemoteTransition (shell transitions) to Launcher.
- *
- * @see IRemoteTransition
- * @see TransitionFilter
+ * Helper class to build {@link RemoteTransition} objects
  */
-@DataClass
-public class RemoteTransitionCompat implements Parcelable {
+public class RemoteTransitionCompat {
     private static final String TAG = "RemoteTransitionCompat";
 
-    @NonNull final RemoteTransition mTransition;
-    @Nullable TransitionFilter mFilter = null;
-
-    RemoteTransitionCompat(RemoteTransition transition) {
-        mTransition = transition;
-    }
-
-    public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner,
-            @NonNull Executor executor, @Nullable IApplicationThread appThread) {
-        IRemoteTransition remote = new IRemoteTransition.Stub() {
-            @Override
-            public void startAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                final Runnable finishAdapter = () ->  {
-                    try {
-                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to call transition finished callback", e);
-                    }
-                };
-                executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter));
-            }
-
-            @Override
-            public void mergeAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                final Runnable finishAdapter = () ->  {
-                    try {
-                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to call transition finished callback", e);
-                    }
-                };
-                executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget,
-                        finishAdapter));
-            }
-        };
-        mTransition = new RemoteTransition(remote, appThread);
-    }
-
     /** Constructor specifically for recents animation */
-    public RemoteTransitionCompat(RecentsAnimationListener recents,
+    public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
             RecentsAnimationControllerCompat controller, IApplicationThread appThread) {
         IRemoteTransition remote = new IRemoteTransition.Stub() {
             final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
@@ -193,25 +137,7 @@
                 mRecentsSession.commitTasksAppearedIfNeeded(recents);
             }
         };
-        mTransition = new RemoteTransition(remote, appThread);
-    }
-
-    /** Adds a filter check that restricts this remote transition to home open transitions. */
-    public void addHomeOpenCheck(ComponentName homeActivity) {
-        if (mFilter == null) {
-            mFilter = new TransitionFilter();
-        }
-        // No need to handle the transition that also dismisses keyguard.
-        mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-        mFilter.mRequirements =
-                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
-                        new TransitionFilter.Requirement()};
-        mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
-        mFilter.mRequirements[0].mTopActivity = homeActivity;
-        mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
-        mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
-        mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
-        mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+        return new RemoteTransition(remote, appThread);
     }
 
     /**
@@ -505,161 +431,4 @@
         @Override public void animateNavigationBarToApp(long duration) {
         }
     }
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @DataClass.Generated.Member
-    /* package-private */ RemoteTransitionCompat(
-            @NonNull RemoteTransition transition,
-            @Nullable TransitionFilter filter) {
-        this.mTransition = transition;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTransition);
-        this.mFilter = filter;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull RemoteTransition getTransition() {
-        return mTransition;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable TransitionFilter getFilter() {
-        return mFilter;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        byte flg = 0;
-        if (mFilter != null) flg |= 0x2;
-        dest.writeByte(flg);
-        dest.writeTypedObject(mTransition, flags);
-        if (mFilter != null) dest.writeTypedObject(mFilter, flags);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    protected RemoteTransitionCompat(@NonNull android.os.Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        byte flg = in.readByte();
-        RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
-        TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR);
-
-        this.mTransition = transition;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTransition);
-        this.mFilter = filter;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR
-            = new Parcelable.Creator<RemoteTransitionCompat>() {
-        @Override
-        public RemoteTransitionCompat[] newArray(int size) {
-            return new RemoteTransitionCompat[size];
-        }
-
-        @Override
-        public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) {
-            return new RemoteTransitionCompat(in);
-        }
-    };
-
-    /**
-     * A builder for {@link RemoteTransitionCompat}
-     */
-    @SuppressWarnings("WeakerAccess")
-    @DataClass.Generated.Member
-    public static class Builder {
-
-        private @NonNull RemoteTransition mTransition;
-        private @Nullable TransitionFilter mFilter;
-
-        private long mBuilderFieldsSet = 0L;
-
-        public Builder(
-                @NonNull RemoteTransition transition) {
-            mTransition = transition;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mTransition);
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull Builder setTransition(@NonNull RemoteTransition value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mTransition = value;
-            return this;
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull Builder setFilter(@NonNull TransitionFilter value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
-            mFilter = value;
-            return this;
-        }
-
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull RemoteTransitionCompat build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x4; // Mark builder used
-
-            if ((mBuilderFieldsSet & 0x2) == 0) {
-                mFilter = null;
-            }
-            RemoteTransitionCompat o = new RemoteTransitionCompat(
-                    mTransition,
-                    mFilter);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x4) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
-        }
-    }
-
-    @DataClass.Generated(
-            time = 1629321609807L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java",
-            inputSignatures = "private static final  java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic  void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate  com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate  android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate  android.window.WindowContainerToken mPausingTask\nprivate  android.window.WindowContainerToken mPipTask\nprivate  android.window.TransitionInfo mInfo\nprivate  android.view.SurfaceControl mOpeningLeash\nprivate  android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate  android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate  android.os.IBinder mTransition\n  void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
deleted file mode 100644
index accc456..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.system;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-/** Interface for something that runs a remote transition animation. */
-public interface RemoteTransitionRunner {
-    /**
-     * Starts a transition animation. Once complete, the implementation should call
-     * `finishCallback`.
-     */
-    void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
-            Runnable finishCallback);
-
-    /**
-     * Attempts to merge a transition into the currently-running animation. If merge is not
-     * possible/supported, this should do nothing. Otherwise, the implementation should call
-     * `finishCallback` immediately to indicate that it merged the transition.
-     *
-     * @param transition The transition that wants to be merged into the running animation.
-     * @param mergeTarget The transition to merge into (that this runner is currently animating).
-     */
-    default void mergeAnimation(IBinder transition, TransitionInfo info,
-            SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index d48d7ff..c9b8712 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -228,7 +228,7 @@
                 listenForDozing(this)
                 if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
                     listenForDozeAmountTransition(this)
-                    listenForGoneToAodTransition(this)
+                    listenForAnyStateToAodTransition(this)
                 } else {
                     listenForDozeAmount(this)
                 }
@@ -286,10 +286,10 @@
      * dozing.
      */
     @VisibleForTesting
-    internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
+    internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            keyguardTransitionInteractor.goneToAodTransition.filter {
-                it.transitionState == TransitionState.STARTED
+            keyguardTransitionInteractor.anyStateToAodTransition.filter {
+                it.transitionState == TransitionState.FINISHED
             }.collect {
                 dozeAmount = 1f
                 clock?.animations?.doze(dozeAmount)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8..a0206f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -35,6 +35,7 @@
     val keyguardOccluded: Boolean,
     val occludingAppRequestingFp: Boolean,
     val primaryUser: Boolean,
+    val shouldListenSfpsState: Boolean,
     val shouldListenForFingerprintAssistant: Boolean,
     val switchingUser: Boolean,
     val udfps: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c756a17..2bb3a5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -106,8 +106,9 @@
     static final int USER_TYPE_WORK_PROFILE = 2;
     static final int USER_TYPE_SECONDARY_USER = 3;
 
-    @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
+    @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
     public @interface Mode {}
+    static final int MODE_UNINITIALIZED = -1;
     static final int MODE_DEFAULT = 0;
     static final int MODE_ONE_HANDED = 1;
     static final int MODE_USER_SWITCHER = 2;
@@ -154,7 +155,11 @@
     private boolean mDisappearAnimRunning;
     private SwipeListener mSwipeListener;
     private ViewMode mViewMode = new DefaultViewMode();
-    private @Mode int mCurrentMode = MODE_DEFAULT;
+    /*
+     * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not
+     * yet been called on it. This will happen when the ViewController is initialized.
+     */
+    private @Mode int mCurrentMode = MODE_UNINITIALIZED;
     private int mWidth = -1;
 
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -347,6 +352,8 @@
 
     private String modeToString(@Mode int mode) {
         switch (mode) {
+            case MODE_UNINITIALIZED:
+                return "Uninitialized";
             case MODE_DEFAULT:
                 return "Default";
             case MODE_ONE_HANDED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 79a01b9..fbb114c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -318,6 +318,7 @@
     @Override
     public void onInit() {
         mSecurityViewFlipperController.init();
+        configureMode();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 39dc609..9d2dcf0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -150,6 +150,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.android.collect.Lists;
 
@@ -321,17 +322,20 @@
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
             mCallbacks = Lists.newArrayList();
     private ContentObserver mDeviceProvisionedObserver;
+    private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
     private final ContentObserver mTimeFormatChangeObserver;
 
     private boolean mSwitchingUser;
 
     private boolean mDeviceInteractive;
+    private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
     private final SubscriptionManager mSubscriptionManager;
     private final TelephonyListenerManager mTelephonyListenerManager;
     private final TrustManager mTrustManager;
     private final UserManager mUserManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final SecureSettings mSecureSettings;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
     private final StatusBarStateController mStatusBarStateController;
@@ -380,6 +384,7 @@
     protected Handler getHandler() {
         return mHandler;
     }
+
     private final Handler mHandler;
 
     private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -704,6 +709,7 @@
 
     /**
      * Request to listen for face authentication when an app is occluding keyguard.
+     *
      * @param request if true and mKeyguardOccluded, request face auth listening, else default
      *                to normal behavior.
      *                See {@link KeyguardUpdateMonitor#shouldListenForFace()}
@@ -716,6 +722,7 @@
 
     /**
      * Request to listen for fingerprint when an app is occluding keyguard.
+     *
      * @param request if true and mKeyguardOccluded, request fingerprint listening, else default
      *                to normal behavior.
      *                See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)}
@@ -1930,6 +1937,7 @@
             Context context,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
+            SecureSettings secureSettings,
             DumpManager dumpManager,
             @Background Executor backgroundExecutor,
             @Main Executor mainExecutor,
@@ -1972,6 +1980,7 @@
         mStatusBarState = mStatusBarStateController.getState();
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
+        mSecureSettings = secureSettings;
         dumpManager.registerDumpable(getClass().getName(), this);
         mSensorPrivacyManager = sensorPrivacyManager;
         mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2214,9 +2223,35 @@
                                 Settings.System.TIME_12_24)));
             }
         };
+
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.TIME_12_24),
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+        updateSfpsRequireScreenOnToAuthPref();
+        mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                updateSfpsRequireScreenOnToAuthPref();
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                mSecureSettings.getUriFor(
+                        Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
+                false,
+                mSfpsRequireScreenOnToAuthPrefObserver,
+                getCurrentUser());
+    }
+
+    protected void updateSfpsRequireScreenOnToAuthPref() {
+        final int defaultSfpsRequireScreenOnToAuthValue =
+                mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+        mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
+                Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+                defaultSfpsRequireScreenOnToAuthValue,
+                getCurrentUser()) != 0;
     }
 
     private void initializeSimState() {
@@ -2261,6 +2296,22 @@
     }
 
     /**
+     * @return true if there's at least one sfps enrollment for the current user.
+     */
+    public boolean isSfpsEnrolled() {
+        return mAuthController.isSfpsEnrolled(getCurrentUser());
+    }
+
+    /**
+     * @return true if sfps HW is supported on this device. Can return true even if the user has
+     * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+     */
+    public boolean isSfpsSupported() {
+        return mAuthController.getSfpsProps() != null
+                && !mAuthController.getSfpsProps().isEmpty();
+    }
+
+    /**
      * @return true if there's at least one face enrolled
      */
     public boolean isFaceEnrolled() {
@@ -2583,13 +2634,21 @@
                 !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
 
         final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+
         final boolean shouldListenUdfpsState = !isUdfps
                 || (!userCanSkipBouncer
-                    && !isEncryptedOrLockdownForUser
-                    && userDoesNotHaveTrust);
+                && !isEncryptedOrLockdownForUser
+                && userDoesNotHaveTrust);
 
-        final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
-                && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+        boolean shouldListenSideFpsState = true;
+        if (isSfpsSupported() && isSfpsEnrolled()) {
+            shouldListenSideFpsState =
+                    mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+        }
+
+        boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+                && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+                && shouldListenSideFpsState;
 
         maybeLogListenerModelData(
                 new KeyguardFingerprintListenModel(
@@ -2611,6 +2670,7 @@
                     mKeyguardOccluded,
                     mOccludingAppRequestingFp,
                     mIsPrimaryUser,
+                    shouldListenSideFpsState,
                     shouldListenForFingerprintAssistant,
                     mSwitchingUser,
                     isUdfps,
@@ -3712,6 +3772,11 @@
             mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
         }
 
+        if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+            mContext.getContentResolver().unregisterContentObserver(
+                    mSfpsRequireScreenOnToAuthPrefObserver);
+        }
+
         try {
             ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
         } catch (RemoteException e) {
@@ -3784,6 +3849,13 @@
                 pw.println("        mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
                 pw.println("        mStatusBarState=" + StatusBarState.toString(mStatusBarState));
                 pw.println("        mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+            } else if (isSfpsSupported()) {
+                pw.println("        sfpsEnrolled=" + isSfpsEnrolled());
+                pw.println("        shouldListenForSfps=" + shouldListenForFingerprint(false));
+                if (isSfpsEnrolled()) {
+                    pw.println("        mSfpsRequireScreenOnToAuthPrefEnabled="
+                        + mSfpsRequireScreenOnToAuthPrefEnabled);
+                }
             }
         }
         if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 313ff4157..0cf3cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -52,7 +52,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -147,7 +147,7 @@
     @NonNull private final WindowManager mWindowManager;
     @NonNull private final DisplayManager mDisplayManager;
     @Nullable private UdfpsController mUdfpsController;
-    @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
+    @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
     @Nullable private SidefpsController mSidefpsController;
     @Nullable private IBiometricContextListener mBiometricContextListener;
     @Nullable private UdfpsLogger mUdfpsLogger;
@@ -160,6 +160,7 @@
 
     @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
     @NonNull private final SparseBooleanArray mFaceEnrolledForUser;
+    @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
     @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -365,6 +366,15 @@
                 }
             }
         }
+        if (mSidefpsProps == null) {
+            Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
+        } else {
+            for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+                if (prop.sensorId == sensorId) {
+                    mSfpsEnrolledForUser.put(userId, hasEnrollments);
+                }
+            }
+        }
         for (Callback cb : mCallbacks) {
             cb.onEnrollmentsChanged(modality);
         }
@@ -722,6 +732,7 @@
         mWindowManager = windowManager;
         mInteractionJankMonitor = jankMonitor;
         mUdfpsEnrolledForUser = new SparseBooleanArray();
+        mSfpsEnrolledForUser = new SparseBooleanArray();
         mFaceEnrolledForUser = new SparseBooleanArray();
         mVibratorHelper = vibrator;
 
@@ -872,21 +883,22 @@
     }
 
     /**
-     * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
+     * Stores the callback received from {@link com.android.server.display.DisplayModeDirector}.
      *
-     * DisplayModeDirector implements {@link IUdfpsHbmListener} and registers it with this class by
-     * calling {@link CommandQueue#setUdfpsHbmListener(IUdfpsHbmListener)}.
+     * DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback}
+     * and registers it with this class by calling
+     * {@link CommandQueue#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)}.
      */
     @Override
-    public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
-        mUdfpsHbmListener = listener;
+    public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
+        mUdfpsRefreshRateRequestCallback = callback;
     }
 
     /**
-     * @return IUdfpsHbmListener that can be set by DisplayModeDirector.
+     * @return IUdfpsRefreshRateRequestCallback that can be set by DisplayModeDirector.
      */
-    @Nullable public IUdfpsHbmListener getUdfpsHbmListener() {
-        return mUdfpsHbmListener;
+    @Nullable public IUdfpsRefreshRateRequestCallback getUdfpsRefreshRateCallback() {
+        return mUdfpsRefreshRateRequestCallback;
     }
 
     @Override
@@ -964,6 +976,11 @@
         return mUdfpsProps;
     }
 
+    @Nullable
+    public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
+        return mSidefpsProps;
+    }
+
     private String getErrorString(@Modality int modality, int error, int vendorCode) {
         switch (modality) {
             case TYPE_FACE:
@@ -1090,6 +1107,17 @@
         return mUdfpsEnrolledForUser.get(userId);
     }
 
+    /**
+     * Whether the passed userId has enrolled SFPS.
+     */
+    public boolean isSfpsEnrolled(int userId) {
+        if (mSidefpsController == null) {
+            return false;
+        }
+
+        return mSfpsEnrolledForUser.get(userId);
+    }
+
     /** If BiometricPrompt is currently being shown to the user. */
     public boolean isShowing() {
         return mCurrentDialog != null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 48c60a0..a091ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.FingerprintManager;
@@ -63,6 +64,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -133,6 +135,7 @@
     @NonNull private final LatencyTracker mLatencyTracker;
     @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
     @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+    @NonNull private final BouncerInteractor mBouncerInteractor;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
@@ -219,7 +222,8 @@
                             mUnlockedScreenOffAnimationController,
                             mUdfpsDisplayMode, requestId, reason, callback,
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
-                                    fromUdfpsView), mActivityLaunchAnimator)));
+                                    fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
+                            mBouncerInteractor)));
         }
 
         @Override
@@ -296,6 +300,30 @@
                 mOverlay.getOverlayView().setDebugMessage(message);
             });
         }
+
+        public Rect getSensorBounds() {
+            return mOverlayParams.getSensorBounds();
+        }
+
+        /**
+         * Passes a mocked MotionEvent to OnTouch.
+         *
+         * @param event MotionEvent to simulate in onTouch
+         */
+        public void debugOnTouch(long requestId, MotionEvent event) {
+            UdfpsController.this.onTouch(requestId, event, false);
+        }
+
+        /**
+         * Debug to run onUiReady
+         */
+        public void debugOnUiReady(long requestId, int sensorId) {
+            if (UdfpsController.this.mAlternateTouchProvider != null) {
+                UdfpsController.this.mAlternateTouchProvider.onUiReady();
+            } else {
+                UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId);
+            }
+        }
     }
 
     /**
@@ -620,7 +648,8 @@
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
             @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
-            @BiometricsBackground Executor biometricsExecutor) {
+            @BiometricsBackground Executor biometricsExecutor,
+            @NonNull BouncerInteractor bouncerInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -651,6 +680,7 @@
         mActivityLaunchAnimator = activityLaunchAnimator;
         mAlternateTouchProvider = alternateTouchProvider.orElse(null);
         mBiometricExecutor = biometricsExecutor;
+        mBouncerInteractor = bouncerInteractor;
 
         mOrientationListener = new BiometricDisplayListener(
                 context,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 7d01096..d70861a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -48,6 +48,8 @@
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -70,29 +72,31 @@
  */
 @UiThread
 class UdfpsControllerOverlay @JvmOverloads constructor(
-    private val context: Context,
-    fingerprintManager: FingerprintManager,
-    private val inflater: LayoutInflater,
-    private val windowManager: WindowManager,
-    private val accessibilityManager: AccessibilityManager,
-    private val statusBarStateController: StatusBarStateController,
-    private val shadeExpansionStateManager: ShadeExpansionStateManager,
-    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val dialogManager: SystemUIDialogManager,
-    private val dumpManager: DumpManager,
-    private val transitionController: LockscreenShadeTransitionController,
-    private val configurationController: ConfigurationController,
-    private val systemClock: SystemClock,
-    private val keyguardStateController: KeyguardStateController,
-    private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
-    private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
-    val requestId: Long,
-    @ShowReason val requestReason: Int,
-    private val controllerCallback: IUdfpsOverlayControllerCallback,
-    private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
-    private val activityLaunchAnimator: ActivityLaunchAnimator,
-    private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
+        private val context: Context,
+        fingerprintManager: FingerprintManager,
+        private val inflater: LayoutInflater,
+        private val windowManager: WindowManager,
+        private val accessibilityManager: AccessibilityManager,
+        private val statusBarStateController: StatusBarStateController,
+        private val shadeExpansionStateManager: ShadeExpansionStateManager,
+        private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        private val dialogManager: SystemUIDialogManager,
+        private val dumpManager: DumpManager,
+        private val transitionController: LockscreenShadeTransitionController,
+        private val configurationController: ConfigurationController,
+        private val systemClock: SystemClock,
+        private val keyguardStateController: KeyguardStateController,
+        private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+        private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+        val requestId: Long,
+        @ShowReason val requestReason: Int,
+        private val controllerCallback: IUdfpsOverlayControllerCallback,
+        private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+        private val activityLaunchAnimator: ActivityLaunchAnimator,
+        private val featureFlags: FeatureFlags,
+        private val bouncerInteractor: BouncerInteractor,
+        private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
@@ -246,7 +250,9 @@
                     unlockedScreenOffAnimationController,
                     dialogManager,
                     controller,
-                    activityLaunchAnimator
+                    activityLaunchAnimator,
+                    featureFlags,
+                    bouncerInteractor
                 )
             }
             REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
index b80b8a0..670a8e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
@@ -46,7 +46,7 @@
             logger.e(TAG, "enable | already requested")
             return
         }
-        if (authController.udfpsHbmListener == null) {
+        if (authController.udfpsRefreshRateCallback == null) {
             logger.e(TAG, "enable | mDisplayManagerCallback is null")
             return
         }
@@ -60,7 +60,7 @@
         try {
             // This method is a misnomer. It has nothing to do with HBM, its purpose is to set
             // the appropriate display refresh rate.
-            authController.udfpsHbmListener!!.onHbmEnabled(request.displayId)
+            authController.udfpsRefreshRateCallback!!.onRequestEnabled(request.displayId)
             logger.v(TAG, "enable | requested optimal refresh rate for UDFPS")
         } catch (e: RemoteException) {
             logger.e(TAG, "enable", e)
@@ -84,7 +84,7 @@
 
         try {
             // Allow DisplayManager to unset the UDFPS refresh rate.
-            authController.udfpsHbmListener!!.onHbmDisabled(request.displayId)
+            authController.udfpsRefreshRateCallback!!.onRequestDisabled(request.displayId)
             logger.v(TAG, "disable | removed the UDFPS refresh rate request")
         } catch (e: RemoteException) {
             logger.e(TAG, "disable", e)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
deleted file mode 100644
index 4d7f89d..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.content.res.Configuration;
-import android.util.MathUtils;
-import android.view.MotionEvent;
-
-import com.android.keyguard.BouncerPanelExpansionCalculator;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.time.SystemClock;
-
-import java.io.PrintWriter;
-
-/**
- * Class that coordinates non-HBM animations during keyguard authentication.
- */
-public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
-    public static final String TAG = "UdfpsKeyguardViewCtrl";
-    @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
-    @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
-    @NonNull private final ConfigurationController mConfigurationController;
-    @NonNull private final SystemClock mSystemClock;
-    @NonNull private final KeyguardStateController mKeyguardStateController;
-    @NonNull private final UdfpsController mUdfpsController;
-    @NonNull private final UnlockedScreenOffAnimationController
-            mUnlockedScreenOffAnimationController;
-    @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
-    private final ValueAnimator mUnlockedScreenOffDozeAnimator = ValueAnimator.ofFloat(0f, 1f);
-
-    private boolean mShowingUdfpsBouncer;
-    private boolean mUdfpsRequested;
-    private float mQsExpansion;
-    private boolean mFaceDetectRunning;
-    private int mStatusBarState;
-    private float mTransitionToFullShadeProgress;
-    private float mLastDozeAmount;
-    private long mLastUdfpsBouncerShowTime = -1;
-    private float mPanelExpansionFraction;
-    private boolean mLaunchTransitionFadingAway;
-    private boolean mIsLaunchingActivity;
-    private float mActivityLaunchProgress;
-
-    /**
-     * hidden amount of pin/pattern/password bouncer
-     * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to
-     * {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f)
-     */
-    private float mInputBouncerHiddenAmount;
-    private boolean mIsGenericBouncerShowing; // whether UDFPS bouncer or input bouncer is visible
-
-    protected UdfpsKeyguardViewController(
-            @NonNull UdfpsKeyguardView view,
-            @NonNull StatusBarStateController statusBarStateController,
-            @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
-            @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
-            @NonNull DumpManager dumpManager,
-            @NonNull LockscreenShadeTransitionController transitionController,
-            @NonNull ConfigurationController configurationController,
-            @NonNull SystemClock systemClock,
-            @NonNull KeyguardStateController keyguardStateController,
-            @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
-            @NonNull SystemUIDialogManager systemUIDialogManager,
-            @NonNull UdfpsController udfpsController,
-            @NonNull ActivityLaunchAnimator activityLaunchAnimator) {
-        super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
-                dumpManager);
-        mKeyguardViewManager = statusBarKeyguardViewManager;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mLockScreenShadeTransitionController = transitionController;
-        mConfigurationController = configurationController;
-        mSystemClock = systemClock;
-        mKeyguardStateController = keyguardStateController;
-        mUdfpsController = udfpsController;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        mActivityLaunchAnimator = activityLaunchAnimator;
-
-        mUnlockedScreenOffDozeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-        mUnlockedScreenOffDozeAnimator.setInterpolator(Interpolators.ALPHA_IN);
-        mUnlockedScreenOffDozeAnimator.addUpdateListener(
-                new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        mView.onDozeAmountChanged(
-                                animation.getAnimatedFraction(),
-                                (float) animation.getAnimatedValue(),
-                                UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF);
-                    }
-                });
-    }
-
-    @Override
-    @NonNull protected String getTag() {
-        return "UdfpsKeyguardViewController";
-    }
-
-    @Override
-    public void onInit() {
-        super.onInit();
-        mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-    }
-
-    @Override
-    protected void onViewAttached() {
-        super.onViewAttached();
-        final float dozeAmount = getStatusBarStateController().getDozeAmount();
-        mLastDozeAmount = dozeAmount;
-        mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
-        getStatusBarStateController().addCallback(mStateListener);
-
-        mUdfpsRequested = false;
-
-        mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
-        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
-        mStatusBarState = getStatusBarStateController().getState();
-        mQsExpansion = mKeyguardViewManager.getQsExpansion();
-        updateGenericBouncerVisibility();
-        mConfigurationController.addCallback(mConfigurationListener);
-        getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener);
-        updateScaleFactor();
-        mView.updatePadding();
-        updateAlpha();
-        updatePauseAuth();
-
-        mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
-        mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
-    }
-
-    @Override
-    protected void onViewDetached() {
-        super.onViewDetached();
-        mFaceDetectRunning = false;
-
-        mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
-        getStatusBarStateController().removeCallback(mStateListener);
-        mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
-        mConfigurationController.removeCallback(mConfigurationListener);
-        getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener);
-        if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
-            mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
-        }
-        mActivityLaunchAnimator.removeListener(mActivityLaunchAnimatorListener);
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        super.dump(pw, args);
-        pw.println("mShowingUdfpsBouncer=" + mShowingUdfpsBouncer);
-        pw.println("mFaceDetectRunning=" + mFaceDetectRunning);
-        pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState));
-        pw.println("mTransitionToFullShadeProgress=" + mTransitionToFullShadeProgress);
-        pw.println("mQsExpansion=" + mQsExpansion);
-        pw.println("mIsGenericBouncerShowing=" + mIsGenericBouncerShowing);
-        pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
-        pw.println("mPanelExpansionFraction=" + mPanelExpansionFraction);
-        pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
-        pw.println("mUdfpsRequested=" + mUdfpsRequested);
-        pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
-        pw.println("mLastDozeAmount=" + mLastDozeAmount);
-
-        mView.dump(pw);
-    }
-
-    /**
-     * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
-     * @return whether the udfpsBouncer has been newly shown or hidden
-     */
-    private boolean showUdfpsBouncer(boolean show) {
-        if (mShowingUdfpsBouncer == show) {
-            return false;
-        }
-
-        boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
-        mShowingUdfpsBouncer = show;
-        if (mShowingUdfpsBouncer) {
-            mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
-        }
-        if (mShowingUdfpsBouncer) {
-            if (udfpsAffordanceWasNotShowing) {
-                mView.animateInUdfpsBouncer(null);
-            }
-
-            if (mKeyguardStateController.isOccluded()) {
-                mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
-            }
-
-            mView.announceForAccessibility(mView.getContext().getString(
-                    R.string.accessibility_fingerprint_bouncer));
-        } else {
-            mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
-        }
-
-        updateGenericBouncerVisibility();
-        updateAlpha();
-        updatePauseAuth();
-        return true;
-    }
-
-    /**
-     * Returns true if the fingerprint manager is running but we want to temporarily pause
-     * authentication. On the keyguard, we may want to show udfps when the shade
-     * is expanded, so this can be overridden with the showBouncer method.
-     */
-    public boolean shouldPauseAuth() {
-        if (mShowingUdfpsBouncer) {
-            return false;
-        }
-
-        if (mUdfpsRequested && !getNotificationShadeVisible()
-                && (!mIsGenericBouncerShowing
-                || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)
-                && mKeyguardStateController.isShowing()) {
-            return false;
-        }
-
-        if (mLaunchTransitionFadingAway) {
-            return true;
-        }
-
-        // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
-        // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
-        // delayed. However, we still animate in the UDFPS affordance with the 
-        // mUnlockedScreenOffDozeAnimator.
-        if (mStatusBarState != KEYGUARD && mLastDozeAmount == 0f) {
-            return true;
-        }
-
-        if (mInputBouncerHiddenAmount < .5f) {
-            return true;
-        }
-
-        if (mView.getUnpausedAlpha() < (255 * .1)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    @Override
-    public boolean listenForTouchesOutsideView() {
-        return true;
-    }
-
-    @Override
-    public void onTouchOutsideView() {
-        maybeShowInputBouncer();
-    }
-
-    /**
-     * If we were previously showing the udfps bouncer, hide it and instead show the regular
-     * (pin/pattern/password) bouncer.
-     *
-     * Does nothing if we weren't previously showing the UDFPS bouncer.
-     */
-    private void maybeShowInputBouncer() {
-        if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
-            mKeyguardViewManager.showBouncer(true);
-        }
-    }
-
-    /**
-     * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
-     * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
-     * bouncer.
-     */
-    private boolean hasUdfpsBouncerShownWithMinTime() {
-        return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
-    }
-
-    /**
-     * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
-     * transitioning yet, while 1.0f means we've fully dragged down.
-     *
-     * For example, start swiping down to expand the notification shade from the empty space in
-     * the middle of the lock screen.
-     */
-    public void setTransitionToFullShadeProgress(float progress) {
-        mTransitionToFullShadeProgress = progress;
-        updateAlpha();
-    }
-
-    /**
-     * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's
-     * alpha is based on the doze amount.
-     */
-    @Override
-    public void updateAlpha() {
-        // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
-        // then the keyguard is occluded by some application - so instead use the input bouncer
-        // hidden amount to determine the fade.
-        float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mPanelExpansionFraction;
-
-        int alpha = mShowingUdfpsBouncer ? 255
-                : (int) MathUtils.constrain(
-                    MathUtils.map(.5f, .9f, 0f, 255f, expansion),
-                    0f, 255f);
-
-        if (!mShowingUdfpsBouncer) {
-            // swipe from top of the lockscreen to expand full QS:
-            alpha *= (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(mQsExpansion));
-
-            // swipe from the middle (empty space) of lockscreen to expand the notification shade:
-            alpha *= (1.0f - mTransitionToFullShadeProgress);
-
-            // Fade out the icon if we are animating an activity launch over the lockscreen and the
-            // activity didn't request the UDFPS.
-            if (mIsLaunchingActivity && !mUdfpsRequested) {
-                alpha *= (1.0f - mActivityLaunchProgress);
-            }
-
-            // Fade out alpha when a dialog is shown
-            // Fade in alpha when a dialog is hidden
-            alpha *= mView.getDialogSuggestedAlpha();
-        }
-        mView.setUnpausedAlpha(alpha);
-    }
-
-    /**
-     * Updates mIsGenericBouncerShowing (whether any bouncer is showing) and updates the
-     * mInputBouncerHiddenAmount to reflect whether the input bouncer is fully showing or not.
-     */
-    private void updateGenericBouncerVisibility() {
-        mIsGenericBouncerShowing = mKeyguardViewManager.isBouncerShowing(); // includes altBouncer
-        final boolean altBouncerShowing = mKeyguardViewManager.isShowingAlternateAuth();
-        if (altBouncerShowing || !mKeyguardViewManager.bouncerIsOrWillBeShowing()) {
-            mInputBouncerHiddenAmount = 1f;
-        } else if (mIsGenericBouncerShowing) {
-            // input bouncer is fully showing
-            mInputBouncerHiddenAmount = 0f;
-        }
-    }
-
-    /**
-     * Update the scale factor based on the device's resolution.
-     */
-    private void updateScaleFactor() {
-        if (mUdfpsController != null && mUdfpsController.mOverlayParams != null) {
-            mView.setScaleFactor(mUdfpsController.mOverlayParams.getScaleFactor());
-        }
-    }
-
-    private final StatusBarStateController.StateListener mStateListener =
-            new StatusBarStateController.StateListener() {
-        @Override
-        public void onDozeAmountChanged(float linear, float eased) {
-            if (mLastDozeAmount < linear) {
-                showUdfpsBouncer(false);
-            }
-            mUnlockedScreenOffDozeAnimator.cancel();
-            final boolean animatingFromUnlockedScreenOff =
-                    mUnlockedScreenOffAnimationController.isAnimationPlaying();
-            if (animatingFromUnlockedScreenOff && linear != 0f) {
-                // we manually animate the fade in of the UDFPS icon since the unlocked
-                // screen off animation prevents the doze amounts to be incrementally eased in
-                mUnlockedScreenOffDozeAnimator.start();
-            } else {
-                mView.onDozeAmountChanged(linear, eased,
-                        UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
-            }
-
-            mLastDozeAmount = linear;
-            updatePauseAuth();
-        }
-
-        @Override
-        public void onStateChanged(int statusBarState) {
-            mStatusBarState = statusBarState;
-            updateAlpha();
-            updatePauseAuth();
-        }
-    };
-
-    private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor =
-            new StatusBarKeyguardViewManager.AlternateAuthInterceptor() {
-                @Override
-                public boolean showAlternateAuthBouncer() {
-                    return showUdfpsBouncer(true);
-                }
-
-                @Override
-                public boolean hideAlternateAuthBouncer() {
-                    return showUdfpsBouncer(false);
-                }
-
-                @Override
-                public boolean isShowingAlternateAuthBouncer() {
-                    return mShowingUdfpsBouncer;
-                }
-
-                @Override
-                public void requestUdfps(boolean request, int color) {
-                    mUdfpsRequested = request;
-                    mView.requestUdfps(request, color);
-                    updateAlpha();
-                    updatePauseAuth();
-                }
-
-                @Override
-                public boolean isAnimating() {
-                    return false;
-                }
-
-                /**
-                 * Set the amount qs is expanded. Forxample, swipe down from the top of the
-                 * lock screen to start the full QS expansion.
-                 */
-                @Override
-                public void setQsExpansion(float qsExpansion) {
-                    mQsExpansion = qsExpansion;
-                    updateAlpha();
-                    updatePauseAuth();
-                }
-
-                @Override
-                public boolean onTouch(MotionEvent event) {
-                    if (mTransitionToFullShadeProgress != 0) {
-                        return false;
-                    }
-                    return mUdfpsController.onTouch(event);
-                }
-
-                @Override
-                public void setBouncerExpansionChanged(float expansion) {
-                    mInputBouncerHiddenAmount = expansion;
-                    updateAlpha();
-                    updatePauseAuth();
-                }
-
-                /**
-                 * Only called on primary auth bouncer changes, not on whether the UDFPS bouncer
-                 * visibility changes.
-                 */
-                @Override
-                public void onBouncerVisibilityChanged() {
-                    updateGenericBouncerVisibility();
-                    updateAlpha();
-                    updatePauseAuth();
-                }
-
-                @Override
-                public void dump(PrintWriter pw) {
-                    pw.println(getTag());
-                }
-            };
-
-    private final ConfigurationController.ConfigurationListener mConfigurationListener =
-            new ConfigurationController.ConfigurationListener() {
-                @Override
-                public void onUiModeChanged() {
-                    mView.updateColor();
-                }
-
-                @Override
-                public void onThemeChanged() {
-                    mView.updateColor();
-                }
-
-                @Override
-                public void onConfigChanged(Configuration newConfig) {
-                    updateScaleFactor();
-                    mView.updatePadding();
-                    mView.updateColor();
-                }
-            };
-
-    private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() {
-        @Override
-        public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
-            float fraction = event.getFraction();
-            mPanelExpansionFraction =
-                    mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
-                            .aboutToShowBouncerProgress(fraction) : fraction;
-            updateAlpha();
-            updatePauseAuth();
-        }
-    };
-
-    private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onLaunchTransitionFadingAwayChanged() {
-                    mLaunchTransitionFadingAway =
-                            mKeyguardStateController.isLaunchTransitionFadingAway();
-                    updatePauseAuth();
-                }
-            };
-
-    private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
-            new ActivityLaunchAnimator.Listener() {
-                @Override
-                public void onLaunchAnimationStart() {
-                    mIsLaunchingActivity = true;
-                    mActivityLaunchProgress = 0f;
-                    updateAlpha();
-                }
-
-                @Override
-                public void onLaunchAnimationEnd() {
-                    mIsLaunchingActivity = false;
-                    updateAlpha();
-                }
-
-                @Override
-                public void onLaunchAnimationProgress(float linearProgress) {
-                    mActivityLaunchProgress = linearProgress;
-                    updateAlpha();
-                }
-            };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..5bae2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.animation.ValueAnimator
+import android.content.res.Configuration
+import android.util.MathUtils
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+open class UdfpsKeyguardViewController
+constructor(
+    private val view: UdfpsKeyguardView,
+    statusBarStateController: StatusBarStateController,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
+    private val keyguardViewManager: StatusBarKeyguardViewManager,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    dumpManager: DumpManager,
+    private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
+    private val configurationController: ConfigurationController,
+    private val systemClock: SystemClock,
+    private val keyguardStateController: KeyguardStateController,
+    private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+    systemUIDialogManager: SystemUIDialogManager,
+    private val udfpsController: UdfpsController,
+    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    featureFlags: FeatureFlags,
+    private val bouncerInteractor: BouncerInteractor
+) :
+    UdfpsAnimationViewController<UdfpsKeyguardView>(
+        view,
+        statusBarStateController,
+        shadeExpansionStateManager,
+        systemUIDialogManager,
+        dumpManager
+    ) {
+    private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
+    private var showingUdfpsBouncer = false
+    private var udfpsRequested = false
+    private var qsExpansion = 0f
+    private var faceDetectRunning = false
+    private var statusBarState = 0
+    private var transitionToFullShadeProgress = 0f
+    private var lastDozeAmount = 0f
+    private var lastUdfpsBouncerShowTime: Long = -1
+    private var panelExpansionFraction = 0f
+    private var launchTransitionFadingAway = false
+    private var isLaunchingActivity = false
+    private var activityLaunchProgress = 0f
+    private val unlockedScreenOffDozeAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
+            interpolator = Interpolators.ALPHA_IN
+            addUpdateListener { animation ->
+                view.onDozeAmountChanged(
+                    animation.animatedFraction,
+                    animation.animatedValue as Float,
+                    UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF
+                )
+            }
+        }
+    /**
+     * Hidden amount of input (pin/pattern/password) bouncer. This is used
+     * [KeyguardBouncer.EXPANSION_VISIBLE] (0f) to [KeyguardBouncer.EXPANSION_HIDDEN] (1f). Only
+     * used for the non-modernBouncer.
+     */
+    private var inputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN
+    private var inputBouncerExpansion = 0f // only used for modernBouncer
+
+    private val stateListener: StatusBarStateController.StateListener =
+        object : StatusBarStateController.StateListener {
+            override fun onDozeAmountChanged(linear: Float, eased: Float) {
+                if (lastDozeAmount < linear) {
+                    showUdfpsBouncer(false)
+                }
+                unlockedScreenOffDozeAnimator.cancel()
+                val animatingFromUnlockedScreenOff =
+                    unlockedScreenOffAnimationController.isAnimationPlaying()
+                if (animatingFromUnlockedScreenOff && linear != 0f) {
+                    // we manually animate the fade in of the UDFPS icon since the unlocked
+                    // screen off animation prevents the doze amounts to be incrementally eased in
+                    unlockedScreenOffDozeAnimator.start()
+                } else {
+                    view.onDozeAmountChanged(
+                        linear,
+                        eased,
+                        UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
+                    )
+                }
+                lastDozeAmount = linear
+                updatePauseAuth()
+            }
+
+            override fun onStateChanged(statusBarState: Int) {
+                this@UdfpsKeyguardViewController.statusBarState = statusBarState
+                updateAlpha()
+                updatePauseAuth()
+            }
+        }
+
+    private val bouncerExpansionCallback: BouncerExpansionCallback =
+        object : BouncerExpansionCallback {
+            override fun onExpansionChanged(expansion: Float) {
+                inputBouncerHiddenAmount = expansion
+                updateAlpha()
+                updatePauseAuth()
+            }
+
+            override fun onVisibilityChanged(isVisible: Boolean) {
+                updateBouncerHiddenAmount()
+                updateAlpha()
+                updatePauseAuth()
+            }
+        }
+
+    private val configurationListener: ConfigurationController.ConfigurationListener =
+        object : ConfigurationController.ConfigurationListener {
+            override fun onUiModeChanged() {
+                view.updateColor()
+            }
+
+            override fun onThemeChanged() {
+                view.updateColor()
+            }
+
+            override fun onConfigChanged(newConfig: Configuration) {
+                updateScaleFactor()
+                view.updatePadding()
+                view.updateColor()
+            }
+        }
+
+    private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
+        panelExpansionFraction =
+            if (keyguardViewManager.isBouncerInTransit) {
+                aboutToShowBouncerProgress(fraction)
+            } else {
+                fraction
+            }
+        updateAlpha()
+        updatePauseAuth()
+    }
+
+    private val keyguardStateControllerCallback: KeyguardStateController.Callback =
+        object : KeyguardStateController.Callback {
+            override fun onLaunchTransitionFadingAwayChanged() {
+                launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+                updatePauseAuth()
+            }
+        }
+
+    private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
+        object : ActivityLaunchAnimator.Listener {
+            override fun onLaunchAnimationStart() {
+                isLaunchingActivity = true
+                activityLaunchProgress = 0f
+                updateAlpha()
+            }
+
+            override fun onLaunchAnimationEnd() {
+                isLaunchingActivity = false
+                updateAlpha()
+            }
+
+            override fun onLaunchAnimationProgress(linearProgress: Float) {
+                activityLaunchProgress = linearProgress
+                updateAlpha()
+            }
+        }
+
+    private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback =
+        object : KeyguardViewManagerCallback {
+            override fun onQSExpansionChanged(qsExpansion: Float) {
+                this@UdfpsKeyguardViewController.qsExpansion = qsExpansion
+                updateAlpha()
+                updatePauseAuth()
+            }
+
+            /**
+             * Forward touches to the UdfpsController. This allows the touch to start from outside
+             * the sensor area and then slide their finger into the sensor area.
+             */
+            override fun onTouch(event: MotionEvent) {
+                // Don't forward touches if the shade has already started expanding.
+                if (transitionToFullShadeProgress != 0f) {
+                    return
+                }
+                udfpsController.onTouch(event)
+            }
+        }
+
+    private val alternateAuthInterceptor: AlternateAuthInterceptor =
+        object : AlternateAuthInterceptor {
+            override fun showAlternateAuthBouncer(): Boolean {
+                return showUdfpsBouncer(true)
+            }
+
+            override fun hideAlternateAuthBouncer(): Boolean {
+                return showUdfpsBouncer(false)
+            }
+
+            override fun isShowingAlternateAuthBouncer(): Boolean {
+                return showingUdfpsBouncer
+            }
+
+            override fun requestUdfps(request: Boolean, color: Int) {
+                udfpsRequested = request
+                view.requestUdfps(request, color)
+                updateAlpha()
+                updatePauseAuth()
+            }
+
+            override fun dump(pw: PrintWriter) {
+                pw.println(tag)
+            }
+        }
+
+    override val tag: String
+        get() = TAG
+
+    override fun onInit() {
+        super.onInit()
+        keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+    }
+
+    init {
+        if (isModernBouncerEnabled) {
+            view.repeatWhenAttached {
+                // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+                // can make the view not visible; and we still want to listen for events
+                // that may make the view visible again.
+                repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+        return scope.launch {
+            bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
+                inputBouncerExpansion = bouncerExpansion
+                updateAlpha()
+                updatePauseAuth()
+            }
+        }
+    }
+
+    public override fun onViewAttached() {
+        super.onViewAttached()
+        val dozeAmount = statusBarStateController.dozeAmount
+        lastDozeAmount = dozeAmount
+        stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
+        statusBarStateController.addCallback(stateListener)
+        udfpsRequested = false
+        launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+        keyguardStateController.addCallback(keyguardStateControllerCallback)
+        statusBarState = statusBarStateController.state
+        qsExpansion = keyguardViewManager.qsExpansion
+        keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
+        if (!isModernBouncerEnabled) {
+            val bouncer = keyguardViewManager.bouncer
+            bouncer?.expansion?.let {
+                bouncerExpansionCallback.onExpansionChanged(it)
+                bouncer.addBouncerExpansionCallback(bouncerExpansionCallback)
+            }
+            updateBouncerHiddenAmount()
+        }
+        configurationController.addCallback(configurationListener)
+        shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
+        updateScaleFactor()
+        view.updatePadding()
+        updateAlpha()
+        updatePauseAuth()
+        keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+        lockScreenShadeTransitionController.udfpsKeyguardViewController = this
+        activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+    }
+
+    override fun onViewDetached() {
+        super.onViewDetached()
+        faceDetectRunning = false
+        keyguardStateController.removeCallback(keyguardStateControllerCallback)
+        statusBarStateController.removeCallback(stateListener)
+        keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor)
+        keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+        configurationController.removeCallback(configurationListener)
+        shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
+        if (lockScreenShadeTransitionController.udfpsKeyguardViewController === this) {
+            lockScreenShadeTransitionController.udfpsKeyguardViewController = null
+        }
+        activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
+        keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
+        if (!isModernBouncerEnabled) {
+            keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback)
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<String>) {
+        super.dump(pw, args)
+        pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
+        pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
+        pw.println("faceDetectRunning=$faceDetectRunning")
+        pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
+        pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
+        pw.println("qsExpansion=$qsExpansion")
+        pw.println("panelExpansionFraction=$panelExpansionFraction")
+        pw.println("unpausedAlpha=" + view.unpausedAlpha)
+        pw.println("udfpsRequestedByApp=$udfpsRequested")
+        pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
+        pw.println("lastDozeAmount=$lastDozeAmount")
+        if (isModernBouncerEnabled) {
+            pw.println("inputBouncerExpansion=$inputBouncerExpansion")
+        } else {
+            pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
+        }
+        view.dump(pw)
+    }
+
+    /**
+     * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
+     * @return whether the udfpsBouncer has been newly shown or hidden
+     */
+    private fun showUdfpsBouncer(show: Boolean): Boolean {
+        if (showingUdfpsBouncer == show) {
+            return false
+        }
+        val udfpsAffordanceWasNotShowing = shouldPauseAuth()
+        showingUdfpsBouncer = show
+        if (showingUdfpsBouncer) {
+            lastUdfpsBouncerShowTime = systemClock.uptimeMillis()
+        }
+        if (showingUdfpsBouncer) {
+            if (udfpsAffordanceWasNotShowing) {
+                view.animateInUdfpsBouncer(null)
+            }
+            if (keyguardStateController.isOccluded) {
+                keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true)
+            }
+            view.announceForAccessibility(
+                view.context.getString(R.string.accessibility_fingerprint_bouncer)
+            )
+        } else {
+            keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+        }
+        updateBouncerHiddenAmount()
+        updateAlpha()
+        updatePauseAuth()
+        return true
+    }
+
+    /**
+     * Returns true if the fingerprint manager is running but we want to temporarily pause
+     * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so
+     * this can be overridden with the showBouncer method.
+     */
+    override fun shouldPauseAuth(): Boolean {
+        if (showingUdfpsBouncer) {
+            return false
+        }
+        if (
+            udfpsRequested &&
+                !notificationShadeVisible &&
+                !isInputBouncerFullyVisible() &&
+                keyguardStateController.isShowing
+        ) {
+            return false
+        }
+        if (launchTransitionFadingAway) {
+            return true
+        }
+
+        // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
+        // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
+        // delayed. However, we still animate in the UDFPS affordance with the
+        // mUnlockedScreenOffDozeAnimator.
+        if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) {
+            return true
+        }
+        if (isBouncerExpansionGreaterThan(.5f)) {
+            return true
+        }
+        return view.unpausedAlpha < 255 * .1
+    }
+
+    fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
+        return if (isModernBouncerEnabled) {
+            inputBouncerExpansion >= bouncerExpansionThreshold
+        } else {
+            inputBouncerHiddenAmount < bouncerExpansionThreshold
+        }
+    }
+
+    fun isInputBouncerFullyVisible(): Boolean {
+        return if (isModernBouncerEnabled) {
+            inputBouncerExpansion == 1f
+        } else {
+            keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth
+        }
+    }
+
+    override fun listenForTouchesOutsideView(): Boolean {
+        return true
+    }
+
+    override fun onTouchOutsideView() {
+        maybeShowInputBouncer()
+    }
+
+    /**
+     * If we were previously showing the udfps bouncer, hide it and instead show the regular
+     * (pin/pattern/password) bouncer.
+     *
+     * Does nothing if we weren't previously showing the UDFPS bouncer.
+     */
+    private fun maybeShowInputBouncer() {
+        if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
+            keyguardViewManager.showBouncer(true)
+        }
+    }
+
+    /**
+     * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the
+     * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer.
+     */
+    private fun hasUdfpsBouncerShownWithMinTime(): Boolean {
+        return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200
+    }
+
+    /**
+     * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
+     * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
+     * to expand the notification shade from the empty space in the middle of the lock screen.
+     */
+    fun setTransitionToFullShadeProgress(progress: Float) {
+        transitionToFullShadeProgress = progress
+        updateAlpha()
+    }
+
+    /**
+     * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is
+     * based on the doze amount.
+     */
+    override fun updateAlpha() {
+        // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
+        // then the keyguard is occluded by some application - so instead use the input bouncer
+        // hidden amount to determine the fade.
+        val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction
+        var alpha: Int =
+            if (showingUdfpsBouncer) 255
+            else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt()
+        if (!showingUdfpsBouncer) {
+            // swipe from top of the lockscreen to expand full QS:
+            alpha =
+                (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion)))
+                    .toInt()
+
+            // swipe from the middle (empty space) of lockscreen to expand the notification shade:
+            alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt()
+
+            // Fade out the icon if we are animating an activity launch over the lockscreen and the
+            // activity didn't request the UDFPS.
+            if (isLaunchingActivity && !udfpsRequested) {
+                alpha = (alpha * (1.0f - activityLaunchProgress)).toInt()
+            }
+
+            // Fade out alpha when a dialog is shown
+            // Fade in alpha when a dialog is hidden
+            alpha = (alpha * view.dialogSuggestedAlpha).toInt()
+        }
+        view.unpausedAlpha = alpha
+    }
+
+    private fun getInputBouncerHiddenAmt(): Float {
+        return if (isModernBouncerEnabled) {
+            1f - inputBouncerExpansion
+        } else {
+            inputBouncerHiddenAmount
+        }
+    }
+
+    /** Update the scale factor based on the device's resolution. */
+    private fun updateScaleFactor() {
+        udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
+    }
+
+    private fun updateBouncerHiddenAmount() {
+        if (isModernBouncerEnabled) {
+            return
+        }
+        val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth
+        if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) {
+            inputBouncerHiddenAmount = 1f
+        } else if (keyguardViewManager.isBouncerShowing) {
+            // input bouncer is fully showing
+            inputBouncerHiddenAmount = 0f
+        }
+    }
+
+    companion object {
+        const val TAG = "UdfpsKeyguardViewController"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index da50f1c..f48cfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics
 
 import android.content.Context
+import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
@@ -27,6 +28,11 @@
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.util.Log
 import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -36,6 +42,8 @@
 private const val TAG = "UdfpsShell"
 private const val REQUEST_ID = 2L
 private const val SENSOR_ID = 0
+private const val MINOR = 10F
+private const val MAJOR = 10F
 
 /**
  * Used to show and hide the UDFPS overlay with statusbar commands.
@@ -67,6 +75,12 @@
             hideUdfpsOverlay()
         } else if (args.size == 2 && args[0] == "show") {
             showOverlay(getEnrollmentReason(args[1]))
+        } else if (args.size == 1 && args[0] == "onUiReady") {
+            onUiReady()
+        } else if (args.size == 1 && args[0] == "simFingerDown") {
+            simFingerDown()
+        } else if (args.size == 1 && args[0] == "simFingerUp") {
+            simFingerUp()
         } else {
             invalidCommand(pw)
         }
@@ -80,6 +94,11 @@
                             "auth-keyguard, auth-other, auth-settings]")
         pw.println("    -> reason otherwise defaults to unknown")
         pw.println("  - hide")
+        pw.println("  - onUiReady")
+        pw.println("  - simFingerDown")
+        pw.println("    -> Simulates onFingerDown on sensor")
+        pw.println("  - simFingerUp")
+        pw.println("    -> Simulates onFingerUp on sensor")
     }
 
     private fun invalidCommand(pw: PrintWriter) {
@@ -125,4 +144,54 @@
     private fun hideOverlay() {
         udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
     }
+
+
+    @VisibleForTesting
+    fun onUiReady() {
+        udfpsOverlayController?.debugOnUiReady(REQUEST_ID, SENSOR_ID)
+    }
+
+    @VisibleForTesting
+    fun simFingerDown() {
+        val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
+
+        val downEvent: MotionEvent? = obtainMotionEvent(ACTION_DOWN, sensorBounds.exactCenterX(),
+                sensorBounds.exactCenterY(), MINOR, MAJOR)
+        udfpsOverlayController?.debugOnTouch(REQUEST_ID, downEvent)
+
+        val moveEvent: MotionEvent? = obtainMotionEvent(ACTION_MOVE, sensorBounds.exactCenterX(),
+                sensorBounds.exactCenterY(), MINOR, MAJOR)
+        udfpsOverlayController?.debugOnTouch(REQUEST_ID, moveEvent)
+
+        downEvent?.recycle()
+        moveEvent?.recycle()
+    }
+
+    @VisibleForTesting
+    fun simFingerUp() {
+        val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
+
+        val upEvent: MotionEvent? = obtainMotionEvent(ACTION_UP, sensorBounds.exactCenterX(),
+                sensorBounds.exactCenterY(), MINOR, MAJOR)
+        udfpsOverlayController?.debugOnTouch(REQUEST_ID, upEvent)
+        upEvent?.recycle()
+    }
+
+    private fun obtainMotionEvent(
+            action: Int,
+            x: Float,
+            y: Float,
+            minor: Float,
+            major: Float
+    ): MotionEvent? {
+        val pp = MotionEvent.PointerProperties()
+        pp.id = 1
+        val pc = MotionEvent.PointerCoords()
+        pc.x = x
+        pc.y = y
+        pc.touchMinor = minor
+        pc.touchMajor = major
+        return MotionEvent.obtain(0, 0, action, 1, arrayOf(pp), arrayOf(pc),
+                0, 0, 1f, 1f, 0, 0, 0, 0)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index fb01691..4eb444e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -25,6 +25,7 @@
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.screenshot.LongScreenshotActivity;
 import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
+import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
 import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
 import com.android.systemui.settings.brightness.BrightnessDialog;
 import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -142,4 +143,11 @@
     @ClassKey(HdmiCecSetMenuLanguageActivity.class)
     public abstract Activity bindHdmiCecSetMenuLanguageActivity(
             HdmiCecSetMenuLanguageActivity activity);
+
+    /** Inject into TvSensorPrivacyChangedActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(TvSensorPrivacyChangedActivity.class)
+    public abstract Activity bindTvSensorPrivacyChangedActivity(
+            TvSensorPrivacyChangedActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 139a8b7..25418c3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.INotificationManager;
@@ -137,6 +138,12 @@
 
     @Provides
     @Singleton
+    static AppOpsManager provideAppOpsManager(Context context) {
+        return context.getSystemService(AppOpsManager.class);
+    }
+
+    @Provides
+    @Singleton
     static AudioManager provideAudioManager(Context context) {
         return context.getSystemService(AudioManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b5d4ecd..2a94e52 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -239,11 +239,13 @@
     @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
 
     // 1100 - windowing
+    @JvmField
     @Keep
     val WM_ENABLE_SHELL_TRANSITIONS =
         SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
 
     /** b/170163464: animate bubbles expanded view collapse with home gesture */
+    @JvmField
     @Keep
     val BUBBLES_HOME_GESTURE =
         SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
@@ -265,40 +267,50 @@
     @Keep
     val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
 
+    @JvmField
     @Keep
     val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
 
+    @JvmField
     @Keep
     val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
 
+    @JvmField
     @Keep
     val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
 
+    @JvmField
     @Keep
     val SHOW_FLOATING_TASKS_AS_BUBBLES =
         SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
 
+    @JvmField
     @Keep
     val ENABLE_FLING_TO_DISMISS_BUBBLE =
         SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
 
+    @JvmField
     @Keep
     val ENABLE_FLING_TO_DISMISS_PIP =
         SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
 
+    @JvmField
     @Keep
     val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
         SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
 
     // 1200 - predictive back
+    @JvmField
     @Keep
     val WM_ENABLE_PREDICTIVE_BACK =
         SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
 
+    @JvmField
     @Keep
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
         SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
 
+    @JvmField
     @Keep
     val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
         SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
@@ -325,7 +337,7 @@
     @JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
 
     // 1700 - clipboard
-    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true)
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
 
     // 1800 - shade container
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 99ae85d..80c6130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -18,6 +18,7 @@
 
 import android.view.KeyEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
 import java.lang.ref.WeakReference
 import javax.inject.Inject
 
@@ -45,4 +46,9 @@
     fun dispatchBackKeyEventPreIme(): Boolean
     fun showNextSecurityScreenOrFinish(): Boolean
     fun resume()
+    fun setDismissAction(
+        onDismissAction: ActivityStarter.OnDismissAction?,
+        cancelAction: Runnable?,
+    )
+    fun willDismissWithActions(): Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 543389e..0046256 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -21,10 +21,9 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -41,9 +40,15 @@
     /** Determines if we want to instantaneously show the bouncer instead of translating. */
     private val _isScrimmed = MutableStateFlow(false)
     val isScrimmed = _isScrimmed.asStateFlow()
-    /** Set amount of how much of the bouncer is showing on the screen */
-    private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
-    val expansionAmount = _expansionAmount.asStateFlow()
+    /**
+     * Set how much of the panel is showing on the screen.
+     * ```
+     *      0f = panel fully hidden = bouncer fully showing
+     *      1f = panel fully showing = bouncer fully hidden
+     * ```
+     */
+    private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN)
+    val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
     private val _isVisible = MutableStateFlow(false)
     val isVisible = _isVisible.asStateFlow()
     private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
@@ -54,8 +59,6 @@
     val hide = _hide.asStateFlow()
     private val _startingToHide = MutableStateFlow(false)
     val startingToHide = _startingToHide.asStateFlow()
-    private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
-    val onDismissAction = _onDismissAction.asStateFlow()
     private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
     val startingDisappearAnimation = _disappearAnimation.asStateFlow()
     private val _keyguardPosition = MutableStateFlow(0f)
@@ -96,8 +99,8 @@
         _isScrimmed.value = isScrimmed
     }
 
-    fun setExpansion(expansion: Float) {
-        _expansionAmount.value = expansion
+    fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
     }
 
     fun setVisible(isVisible: Boolean) {
@@ -120,10 +123,6 @@
         _startingToHide.value = startingToHide
     }
 
-    fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
-        _onDismissAction.value = bouncerCallbackActionsModel
-    }
-
     fun setStartDisappearAnimation(runnable: Runnable?) {
         _disappearAnimation.value = runnable
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 6baaf5f..c867c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -23,9 +23,12 @@
 import com.android.systemui.doze.DozeHost
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -65,6 +68,9 @@
      */
     val isKeyguardShowing: Flow<Boolean>
 
+    /** Observable for whether the bouncer is showing. */
+    val isBouncerShowing: Flow<Boolean>
+
     /**
      * Observable for whether we are in doze state.
      *
@@ -95,6 +101,9 @@
     /** Observable for device wake/sleep state */
     val wakefulnessState: Flow<WakefulnessModel>
 
+    /** Observable for biometric unlock modes */
+    val biometricUnlockState: Flow<BiometricUnlockModel>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -125,6 +134,7 @@
     private val keyguardStateController: KeyguardStateController,
     dozeHost: DozeHost,
     wakefulnessLifecycle: WakefulnessLifecycle,
+    biometricUnlockController: BiometricUnlockController,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -159,6 +169,29 @@
         awaitClose { keyguardStateController.removeCallback(callback) }
     }
 
+    override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardStateController.Callback {
+                override fun onBouncerShowingChanged() {
+                    trySendWithFailureLogging(
+                        keyguardStateController.isBouncerShowing,
+                        TAG,
+                        "updated isBouncerShowing"
+                    )
+                }
+            }
+
+        keyguardStateController.addCallback(callback)
+        // Adding the callback does not send an initial update.
+        trySendWithFailureLogging(
+            keyguardStateController.isBouncerShowing,
+            TAG,
+            "initial isBouncerShowing"
+        )
+
+        awaitClose { keyguardStateController.removeCallback(callback) }
+    }
+
     override val isDozing: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
@@ -248,6 +281,24 @@
         awaitClose { wakefulnessLifecycle.removeObserver(callback) }
     }
 
+    override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+        val callback =
+            object : BiometricUnlockController.BiometricModeListener {
+                override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+                    trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+                }
+            }
+
+        biometricUnlockController.addBiometricModeListener(callback)
+        trySendWithFailureLogging(
+            biometricModeIntToObject(biometricUnlockController.getMode()),
+            TAG,
+            "initial biometric mode"
+        )
+
+        awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -279,6 +330,20 @@
         }
     }
 
+    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+        return when (value) {
+            0 -> BiometricUnlockModel.NONE
+            1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
+            2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+            3 -> BiometricUnlockModel.SHOW_BOUNCER
+            4 -> BiometricUnlockModel.ONLY_WAKE
+            5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
+            6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+            7 -> BiometricUnlockModel.DISMISS_BOUNCER
+            else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+        }
+    }
+
     companion object {
         private const val TAG = "KeyguardRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..7e01db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodToGoneTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("AOD->GONE") {
+
+    private val wakeAndUnlockModes =
+        setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.biometricUnlockState
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (biometricUnlockState, keyguardState) = pair
+                    if (
+                        keyguardState == KeyguardState.AOD &&
+                            wakeAndUnlockModes.contains(biometricUnlockState)
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.AOD,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 500L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 2af9318..dbb0352 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.plugins.ActivityStarter
@@ -40,6 +39,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
@@ -77,7 +77,7 @@
             KeyguardBouncerModel(
                 promptReason = repository.bouncerPromptReason ?: 0,
                 errorMessage = repository.bouncerErrorMessage,
-                expansionAmount = repository.expansionAmount.value
+                expansionAmount = repository.panelExpansionAmount.value
             )
         )
         repository.setShowingSoon(false)
@@ -90,14 +90,22 @@
     val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
     val isVisible: Flow<Boolean> = repository.isVisible
     val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
-    val expansionAmount: Flow<Float> = repository.expansionAmount
     val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
     val startingDisappearAnimation: Flow<Runnable> =
         repository.startingDisappearAnimation.filterNotNull()
-    val onDismissAction: Flow<BouncerCallbackActionsModel> =
-        repository.onDismissAction.filterNotNull()
     val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
     val keyguardPosition: Flow<Float> = repository.keyguardPosition
+    val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+    /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
+    val bouncerExpansion: Flow<Float> = //
+        combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible
+            ->
+            if (isVisible) {
+                1f - expansionAmount
+            } else {
+                0f
+            }
+        }
 
     // TODO(b/243685699): Move isScrimmed logic to data layer.
     // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
@@ -128,7 +136,7 @@
         Trace.beginSection("KeyguardBouncer#show")
         repository.setScrimmed(isScrimmed)
         if (isScrimmed) {
-            setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+            setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
         }
 
         if (resumeBouncer) {
@@ -149,7 +157,6 @@
         }
         keyguardStateController.notifyBouncerShowing(true)
         callbackInteractor.dispatchStartingToShow()
-
         Trace.endSection()
     }
 
@@ -168,7 +175,6 @@
         keyguardStateController.notifyBouncerShowing(false /* showing */)
         cancelShowRunnable()
         repository.setShowingSoon(false)
-        repository.setOnDismissAction(null)
         repository.setVisible(false)
         repository.setHide(true)
         repository.setShow(null)
@@ -176,14 +182,17 @@
     }
 
     /**
-     * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
-     * where 0f => showing and 1f => hiding
+     * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
+     * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
+     * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
+     * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
+     * of 0f represents the bouncer fully showing.
      */
-    fun setExpansion(expansion: Float) {
-        val oldExpansion = repository.expansionAmount.value
+    fun setPanelExpansion(expansion: Float) {
+        val oldExpansion = repository.panelExpansionAmount.value
         val expansionChanged = oldExpansion != expansion
         if (repository.startingDisappearAnimation.value == null) {
-            repository.setExpansion(expansion)
+            repository.setPanelExpansion(expansion)
         }
 
         if (
@@ -227,7 +236,7 @@
         onDismissAction: ActivityStarter.OnDismissAction?,
         cancelAction: Runnable?
     ) {
-        repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+        bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
     }
 
     /** Update the resources of the views. */
@@ -282,7 +291,7 @@
     /** Returns whether bouncer is fully showing. */
     fun isFullyShowing(): Boolean {
         return (repository.showingSoon.value || repository.isVisible.value) &&
-            repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+            repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
             repository.startingDisappearAnimation.value == null
     }
 
@@ -294,8 +303,8 @@
     /** If bouncer expansion is between 0f and 1f non-inclusive. */
     fun isInTransit(): Boolean {
         return repository.showingSoon.value ||
-            repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
-                repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+            repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+                repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
     }
 
     /** Return whether bouncer is animating away. */
@@ -305,7 +314,7 @@
 
     /** Return whether bouncer will dismiss with actions */
     fun willDismissWithAction(): Boolean {
-        return repository.onDismissAction.value?.onDismissAction != null
+        return bouncerView.delegate?.willDismissWithActions() == true
     }
 
     /** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 03c6a78..614ff8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -39,10 +41,19 @@
     val dozeAmount: Flow<Float> = repository.dozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
-    /** Whether the keyguard is showing to not. */
+    /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    /** Whether the bouncer is showing or not. */
+    val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
     /** The device wake/sleep state */
     val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+    /** Observable for the [StatusBarState] */
+    val statusBarState: Flow<StatusBarState> = repository.statusBarState
+    /**
+     * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
+     * side, under display) is used to unlock the device.
+     */
+    val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
 
     fun isKeyguardShowing(): Boolean {
         return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 83d9432..57fb4a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,11 +41,13 @@
         }
 
         scope.launch {
-            interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) }
+            interactor.finishedKeyguardTransitionStep.collect {
+                logger.i("Finished transition", it)
+            }
         }
 
         scope.launch {
-            interactor.startedKeyguardState.collect { logger.i("Started transition to", it) }
+            interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index d5ea77b..a7c6d44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -41,6 +41,7 @@
                     is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
                     is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
                     is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
                 }
             it.start()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index dffd097..749183e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -44,8 +43,9 @@
     /** LOCKSCREEN->AOD transition information. */
     val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
 
-    /** GONE->AOD information. */
-    val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+    /** (any)->AOD transition information */
+    val anyStateToAodTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == KeyguardState.AOD }
 
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -57,15 +57,15 @@
             lockscreenToAodTransition,
         )
 
+    /* The last [TransitionStep] with a [TransitionState] of FINISHED */
+    val finishedKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
+
     /* The last completed [KeyguardState] transition */
     val finishedKeyguardState: Flow<KeyguardState> =
-        repository.transitions
-            .filter { step -> step.transitionState == TransitionState.FINISHED }
-            .map { step -> step.to }
+        finishedKeyguardTransitionStep.map { step -> step.to }
 
-    /* The last started [KeyguardState] transition */
-    val startedKeyguardState: Flow<KeyguardState> =
-        repository.transitions
-            .filter { step -> step.transitionState == TransitionState.STARTED }
-            .map { step -> step.to }
+    /* The last [TransitionStep] with a [TransitionState] of STARTED */
+    val startedKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 761f3fd..fd4814d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -16,14 +16,16 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
@@ -38,7 +40,7 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
-    private val keyguardRepository: KeyguardRepository,
+    private val keyguardInteractor: KeyguardInteractor,
     private val shadeRepository: ShadeRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -47,12 +49,47 @@
     private var transitionId: UUID? = null
 
     override fun start() {
+        listenForDraggingUpToBouncer()
+        listenForBouncerHiding()
+    }
+
+    private fun listenForBouncerHiding() {
+        scope.launch {
+            keyguardInteractor.isBouncerShowing
+                .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (isBouncerShowing, wakefulnessState) = pair
+                    if (!isBouncerShowing) {
+                        val to =
+                            if (
+                                wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
+                                    wakefulnessState == WakefulnessModel.ASLEEP
+                            ) {
+                                KeyguardState.AOD
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.BOUNCER,
+                                to = to,
+                                animator = getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+    private fun listenForDraggingUpToBouncer() {
         scope.launch {
             shadeRepository.shadeModel
                 .sample(
                     combine(
                         keyguardTransitionInteractor.finishedKeyguardState,
-                        keyguardRepository.statusBarState,
+                        keyguardInteractor.statusBarState,
                     ) { keyguardState, statusBarState ->
                         Pair(keyguardState, statusBarState)
                     },
@@ -100,4 +137,15 @@
                 }
         }
     }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 300L
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 728bafa..37f33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -42,6 +42,8 @@
 
     @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
 
+    @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+
     @Binds
     @IntoSet
     abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
new file mode 100644
index 0000000..db709b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Model device wakefulness states. */
+enum class BiometricUnlockModel {
+    /** Mode in which we don't need to wake up the device when we authenticate. */
+    NONE,
+    /**
+     * Mode in which we wake up the device, and directly dismiss Keyguard. Active when we acquire a
+     * fingerprint while the screen is off and the device was sleeping.
+     */
+    WAKE_AND_UNLOCK,
+    /**
+     * Mode in which we wake the device up, and fade out the Keyguard contents because they were
+     * already visible while pulsing in doze mode.
+     */
+    WAKE_AND_UNLOCK_PULSING,
+    /**
+     * Mode in which we wake up the device, but play the normal dismiss animation. Active when we
+     * acquire a fingerprint pulsing in doze mode.
+     */
+    SHOW_BOUNCER,
+    /**
+     * Mode in which we only wake up the device, and keyguard was not showing when we authenticated.
+     */
+    ONLY_WAKE,
+    /**
+     * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
+     * device while being requested when keyguard is occluded or showing.
+     */
+    UNLOCK_COLLAPSING,
+    /** When bouncer is visible and will be dismissed. */
+    DISMISS_BOUNCER,
+    /** Mode in which fingerprint wakes and unlocks the device from a dream. */
+    WAKE_AND_UNLOCK_FROM_DREAM,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0ca3582..732a6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -21,10 +21,11 @@
     val to: KeyguardState = KeyguardState.NONE,
     val value: Float = 0f, // constrained [0.0, 1.0]
     val transitionState: TransitionState = TransitionState.FINISHED,
+    val ownerName: String = "",
 ) {
     constructor(
         info: TransitionInfo,
         value: Float,
         transitionState: TransitionState,
-    ) : this(info.from, info.to, value, transitionState)
+    ) : this(info.from, info.to, value, transitionState, info.ownerName)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index df26014..a22958b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.collect
@@ -75,6 +76,17 @@
                     hostViewController.showPrimarySecurityScreen()
                     hostViewController.onResume()
                 }
+
+                override fun setDismissAction(
+                    onDismissAction: ActivityStarter.OnDismissAction?,
+                    cancelAction: Runnable?
+                ) {
+                    hostViewController.setOnDismissAction(onDismissAction, cancelAction)
+                }
+
+                override fun willDismissWithActions(): Boolean {
+                    return hostViewController.hasDismissActions()
+                }
             }
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -122,15 +134,6 @@
                     }
 
                     launch {
-                        viewModel.setDismissAction.collect {
-                            hostViewController.setOnDismissAction(
-                                it.onDismissAction,
-                                it.cancelAction
-                            )
-                        }
-                    }
-
-                    launch {
                         viewModel.startDisappearAnimation.collect {
                             hostViewController.startDisappearAnimation(it)
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9ad5211..9a92843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
@@ -38,7 +37,7 @@
     private val interactor: BouncerInteractor,
 ) {
     /** Observe on bouncer expansion amount. */
-    val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
+    val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
 
     /** Observe on bouncer visibility. */
     val isBouncerVisible: Flow<Boolean> = interactor.isVisible
@@ -63,9 +62,6 @@
     /** Observe whether bouncer is starting to hide. */
     val startingToHide: Flow<Unit> = interactor.startingToHide
 
-    /** Observe whether we want to set the dismiss action to the bouncer. */
-    val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
-
     /** Observe whether we want to start the disappear animation. */
     val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 50cf63d..b9f5859 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -87,14 +87,18 @@
 import android.view.HapticFeedbackConstants;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
 import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.SurfaceChangedCallback;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -366,15 +370,6 @@
         }
 
         @Override
-        public void onQuickStepStarted() {
-            // Use navbar dragging as a signal to hide the rotate button
-            mView.getRotationButtonController().setRotateSuggestionButtonState(false);
-
-            // Hide the notifications panel when quick step starts
-            mShadeController.collapsePanel(true /* animate */);
-        }
-
-        @Override
         public void onPrioritizedRotation(@Surface.Rotation int rotation) {
             mStartingQuickSwitchRotation = rotation;
             if (rotation == -1) {
@@ -487,6 +482,24 @@
                 }
             };
 
+    private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
+            new SurfaceChangedCallback() {
+            @Override
+            public void surfaceCreated(Transaction t) {
+                notifyNavigationBarSurface();
+            }
+
+            @Override
+            public void surfaceDestroyed() {
+                notifyNavigationBarSurface();
+            }
+
+            @Override
+            public void surfaceReplaced(Transaction t) {
+                notifyNavigationBarSurface();
+            }
+    };
+
     @Inject
     NavigationBar(
             NavigationBarView navigationBarView,
@@ -696,7 +709,8 @@
         final Display display = mView.getDisplay();
         mView.setComponents(mRecentsOptional);
         if (mCentralSurfacesOptionalLazy.get().isPresent()) {
-            mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+            mView.setComponents(
+                    mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController());
         }
         mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
         mView.setOnVerticalChangedListener(this::onVerticalChanged);
@@ -717,6 +731,8 @@
 
         mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
                 mOnComputeInternalInsetsListener);
+        mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
+        notifyNavigationBarSurface();
 
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -795,6 +811,10 @@
         mHandler.removeCallbacks(mEnableLayoutTransitions);
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
+        ViewRootImpl viewRoot = mView.getViewRootImpl();
+        if (viewRoot != null) {
+            viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+        }
         mFrame = null;
         mOrientationHandle = null;
     }
@@ -947,6 +967,12 @@
         }
     }
 
+    private void notifyNavigationBarSurface() {
+        ViewRootImpl viewRoot = mView.getViewRootImpl();
+        SurfaceControl surface = viewRoot != null ? viewRoot.getSurfaceControl() : null;
+        mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
+    }
+
     private int deltaRotation(int oldRotation, int newRotation) {
         int delta = newRotation - oldRotation;
         if (delta < 0) delta += 4;
@@ -1059,7 +1085,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         if (displayId != mDisplayId) {
             return;
@@ -1272,8 +1298,8 @@
     }
 
     private void onVerticalChanged(boolean isVertical) {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                statusBar -> statusBar.setQsScrimEnabled(!isVertical));
+        mCentralSurfacesOptionalLazy.get().ifPresent(statusBar ->
+                statusBar.getNotificationPanelViewController().setQsScrimEnabled(!isVertical));
     }
 
     private boolean onNavigationTouch(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 73fc21e..eb87ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -49,8 +49,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Display;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -355,7 +355,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName,
+            @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
         boolean nbModeChanged = false;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 622f5a2..83c2a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -412,10 +412,6 @@
         logSomePresses(action, flags);
         if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
             Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
-            if (action == MotionEvent.ACTION_UP) {
-                mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
-                        -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
-            }
         }
         final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
         final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
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 10ff48b..7964d16 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -289,8 +289,6 @@
                         mBackAnimation.setTriggerBack(true);
                     }
 
-                    mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
-                            (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
                     logGesture(mInRejectedExclusion
                             ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
                             : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
@@ -302,8 +300,6 @@
                         mBackAnimation.setTriggerBack(false);
                     }
                     logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
-                    mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
-                            (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
                 }
 
                 @Override
@@ -804,9 +800,6 @@
 
         if (mExcludeRegion.contains(x, y)) {
             if (withinRange) {
-                // Log as exclusion only if it is in acceptable range in the first place.
-                mOverviewProxyService.notifyBackAction(
-                        false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
                 // We don't have the end point for logging purposes.
                 mEndPoint.x = -1;
                 mEndPoint.y = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 703b95a..b5ceeae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -19,6 +19,7 @@
 import android.annotation.StyleRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
@@ -33,6 +34,7 @@
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.util.LargeScreenUtils;
 
 import java.util.Objects;
 
@@ -72,6 +74,7 @@
         mMobileSignal = findViewById(R.id.mobile_signal);
         mCarrierText = findViewById(R.id.qs_carrier_text);
         mSpacer = findViewById(R.id.spacer);
+        updateResources();
     }
 
     /**
@@ -142,4 +145,20 @@
     public void updateTextAppearance(@StyleRes int resId) {
         FontSizeUtils.updateFontSizeFromStyle(mCarrierText, resId);
     }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateResources();
+    }
+
+    private void updateResources() {
+        boolean useLargeScreenHeader =
+                LargeScreenUtils.shouldUseLargeScreenShadeHeader(getResources());
+        mCarrierText.setMaxEms(
+                useLargeScreenHeader
+                        ? Integer.MAX_VALUE
+                        : getResources().getInteger(R.integer.qs_carrier_max_em)
+        );
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e9a6c25..1f92b12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -140,7 +140,7 @@
             iv.setTag(R.id.qs_icon_tag, icon);
             iv.setTag(R.id.qs_slash_tag, state.slash);
             iv.setPadding(0, padding, 0, padding);
-            if (d instanceof Animatable2) {
+            if (shouldAnimate && d instanceof Animatable2) {
                 Animatable2 a = (Animatable2) d;
                 a.start();
                 if (state.isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66be00d..46c4f41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -64,6 +64,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.InputMethodManager;
 
@@ -149,6 +150,7 @@
     private final UiEventLogger mUiEventLogger;
 
     private Region mActiveNavBarRegion;
+    private SurfaceControl mNavigationBarSurface;
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
@@ -190,7 +192,8 @@
                 // TODO move this logic to message queue
                 mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
                     if (event.getActionMasked() == ACTION_DOWN) {
-                        centralSurfaces.getPanelController().startExpandLatencyTracking();
+                        centralSurfaces.getNotificationPanelViewController()
+                                        .startExpandLatencyTracking();
                     }
                     mHandler.post(() -> {
                         int action = event.getActionMasked();
@@ -217,17 +220,15 @@
         }
 
         @Override
-        public void onBackPressed() throws RemoteException {
+        public void onBackPressed() {
             verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
                 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
                 sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-
-                notifyBackAction(true, -1, -1, true, false);
             });
         }
 
         @Override
-        public void onImeSwitcherPressed() throws RemoteException {
+        public void onImeSwitcherPressed() {
             // TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since
             // Launcher/Taskbar isn't display aware.
             mContext.getSystemService(InputMethodManager.class)
@@ -316,12 +317,6 @@
         }
 
         @Override
-        public void notifySwipeUpGestureStarted() {
-            verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
-                    notifySwipeUpGestureStartedInternal());
-        }
-
-        @Override
         public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
             verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
                     notifyPrioritizedRotationInternal(rotation));
@@ -443,6 +438,7 @@
                 Log.e(TAG_OPS, "Failed to call onInitialize()", e);
             }
             dispatchNavButtonBounds();
+            dispatchNavigationBarSurface();
 
             // Force-update the systemui state flags
             updateSystemUiStateFlags();
@@ -597,11 +593,18 @@
                 .commitUpdate(mContext.getDisplayId());
     }
 
-    public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
-            boolean gestureSwipeLeft) {
+    /**
+     * Called when the navigation bar surface is created or changed
+     */
+    public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
+        mNavigationBarSurface = navbarSurface;
+        dispatchNavigationBarSurface();
+    }
+
+    private void dispatchNavigationBarSurface() {
         try {
             if (mOverviewProxy != null) {
-                mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
+                mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
             }
         } catch (RemoteException e) {
             Log.e(TAG_OPS, "Failed to notify back action", e);
@@ -614,7 +617,7 @@
         final NavigationBarView navBarView =
                 mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
         final NotificationPanelViewController panelController =
-                mCentralSurfacesOptionalLazy.get().get().getPanelController();
+                mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController();
         if (SysUiState.DEBUG) {
             Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
                     + " navBarView=" + navBarView + " panelController=" + panelController);
@@ -800,24 +803,12 @@
         }
     }
 
-    public void notifyQuickStepStarted() {
-        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
-            mConnectionCallbacks.get(i).onQuickStepStarted();
-        }
-    }
-
     private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) {
         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
             mConnectionCallbacks.get(i).onPrioritizedRotation(rotation);
         }
     }
 
-    public void notifyQuickScrubStarted() {
-        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
-            mConnectionCallbacks.get(i).onQuickScrubStarted();
-        }
-    }
-
     private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
             mConnectionCallbacks.get(i).onAssistantProgress(progress);
@@ -836,12 +827,6 @@
         }
     }
 
-    private void notifySwipeUpGestureStartedInternal() {
-        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
-            mConnectionCallbacks.get(i).onSwipeUpGestureStarted();
-        }
-    }
-
     public void notifyAssistantVisibilityChanged(float visibility) {
         try {
             if (mOverviewProxy != null) {
@@ -1005,23 +990,20 @@
         pw.print("  mWindowCornerRadius="); pw.println(mWindowCornerRadius);
         pw.print("  mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
         pw.print("  mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
+        pw.print("  mNavigationBarSurface="); pw.println(mNavigationBarSurface);
         pw.print("  mNavBarMode="); pw.println(mNavBarMode);
         mSysUiState.dump(pw, args);
     }
 
     public interface OverviewProxyListener {
         default void onConnectionChanged(boolean isConnected) {}
-        default void onQuickStepStarted() {}
-        default void onSwipeUpGestureStarted() {}
         default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
         default void onOverviewShown(boolean fromHome) {}
-        default void onQuickScrubStarted() {}
         /** Notify the recents app (overview) is started by 3-button navigation. */
         default void onToggleRecentApps() {}
         default void onHomeRotationEnabled(boolean enabled) {}
         default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
         default void onTaskbarAutohideSuspend(boolean suspend) {}
-        default void onSystemUiStateChanged(int sysuiStateFlags) {}
         default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
         default void onAssistantGestureCompletion(float velocity) {}
         default void startAssistant(Bundle bundle) {}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
new file mode 100644
index 0000000..731b177
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.sensorprivacy.television;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+
+import android.annotation.DimenRes;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.hardware.SensorPrivacyManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.tv.TvBottomSheetActivity;
+import com.android.systemui.util.settings.GlobalSettings;
+
+import javax.inject.Inject;
+
+/**
+ * Bottom sheet that is shown when the camera/mic sensors privacy state changed
+ * by the global software toggle or physical privacy switch.
+ */
+public class TvSensorPrivacyChangedActivity extends TvBottomSheetActivity {
+
+    private static final String TAG = TvSensorPrivacyChangedActivity.class.getSimpleName();
+
+    private static final int ALL_SENSORS = Integer.MAX_VALUE;
+
+    private int mSensor = -1;
+    private int mToggleType = -1;
+
+    private final GlobalSettings mGlobalSettings;
+    private final IndividualSensorPrivacyController mSensorPrivacyController;
+    private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
+    private TextView mTitle;
+    private TextView mContent;
+    private ImageView mIcon;
+    private ImageView mSecondIcon;
+    private Button mPositiveButton;
+    private Button mCancelButton;
+
+    @Inject
+    public TvSensorPrivacyChangedActivity(
+            IndividualSensorPrivacyController individualSensorPrivacyController,
+            GlobalSettings globalSettings) {
+        mSensorPrivacyController = individualSensorPrivacyController;
+        mGlobalSettings = globalSettings;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+        boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
+                false);
+        if (allSensors) {
+            mSensor = ALL_SENSORS;
+        } else {
+            mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1);
+        }
+
+        mToggleType = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_TOGGLE_TYPE, -1);
+
+        if (mSensor == -1 || mToggleType == -1) {
+            Log.v(TAG, "Invalid extras");
+            finish();
+            return;
+        }
+
+        // Do not show for software toggles
+        if (mToggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
+            finish();
+            return;
+        }
+
+        mSensorPrivacyCallback = (sensor, blocked) -> {
+            updateUI();
+        };
+
+        initUI();
+    }
+
+    private void initUI() {
+        mTitle = findViewById(R.id.bottom_sheet_title);
+        mContent = findViewById(R.id.bottom_sheet_body);
+        mIcon = findViewById(R.id.bottom_sheet_icon);
+        // mic icon if both icons are shown
+        mSecondIcon = findViewById(R.id.bottom_sheet_second_icon);
+        mPositiveButton = findViewById(R.id.bottom_sheet_positive_button);
+        mCancelButton = findViewById(R.id.bottom_sheet_negative_button);
+
+        mCancelButton.setText(android.R.string.cancel);
+        mCancelButton.setOnClickListener(v -> finish());
+
+        updateUI();
+    }
+
+    private void updateUI() {
+        final Resources resources = getResources();
+        setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
+        setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
+
+        switch (mSensor) {
+            case CAMERA:
+                updateUiForCameraUpdate(
+                        mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA));
+                break;
+            case MICROPHONE:
+            default:
+                updateUiForMicUpdate(
+                        mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE));
+                break;
+        }
+
+        // Start animation if drawable is animated
+        Drawable iconDrawable = mIcon.getDrawable();
+        if (iconDrawable instanceof Animatable) {
+            ((Animatable) iconDrawable).start();
+        }
+
+        mPositiveButton.setVisibility(View.GONE);
+        mCancelButton.setText(android.R.string.ok);
+    }
+
+    private void updateUiForMicUpdate(boolean blocked) {
+        if (blocked) {
+            mTitle.setText(R.string.sensor_privacy_mic_turned_off_dialog_title);
+            if (isExplicitUserInteractionAudioBypassAllowed()) {
+                mContent.setText(R.string.sensor_privacy_mic_blocked_with_exception_dialog_content);
+            } else {
+                mContent.setText(R.string.sensor_privacy_mic_blocked_no_exception_dialog_content);
+            }
+            mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
+            mSecondIcon.setVisibility(View.GONE);
+        } else {
+            mTitle.setText(R.string.sensor_privacy_mic_turned_on_dialog_title);
+            mContent.setText(R.string.sensor_privacy_mic_unblocked_dialog_content);
+            mIcon.setImageResource(com.android.internal.R.drawable.ic_mic_allowed);
+            mSecondIcon.setVisibility(View.GONE);
+        }
+    }
+
+    private void updateUiForCameraUpdate(boolean blocked) {
+        if (blocked) {
+            mTitle.setText(R.string.sensor_privacy_camera_turned_off_dialog_title);
+            mContent.setText(R.string.sensor_privacy_camera_blocked_dialog_content);
+            mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
+            mSecondIcon.setVisibility(View.GONE);
+        } else {
+            mTitle.setText(R.string.sensor_privacy_camera_turned_on_dialog_title);
+            mContent.setText(R.string.sensor_privacy_camera_unblocked_dialog_content);
+            mIcon.setImageResource(com.android.internal.R.drawable.ic_camera_allowed);
+            mSecondIcon.setVisibility(View.GONE);
+        }
+    }
+
+    private void setIconTint(boolean enableTint) {
+        final Resources resources = getResources();
+
+        if (enableTint) {
+            final ColorStateList iconTint = resources.getColorStateList(
+                    R.color.bottom_sheet_icon_color, getTheme());
+            mIcon.setImageTintList(iconTint);
+            mSecondIcon.setImageTintList(iconTint);
+        } else {
+            mIcon.setImageTintList(null);
+            mSecondIcon.setImageTintList(null);
+        }
+
+        mIcon.invalidate();
+        mSecondIcon.invalidate();
+    }
+
+    private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) {
+        final Resources resources = getResources();
+        final int iconWidth = resources.getDimensionPixelSize(widthRes);
+        final int iconHeight = resources.getDimensionPixelSize(heightRes);
+
+        mIcon.getLayoutParams().width = iconWidth;
+        mIcon.getLayoutParams().height = iconHeight;
+        mIcon.invalidate();
+
+        mSecondIcon.getLayoutParams().width = iconWidth;
+        mSecondIcon.getLayoutParams().height = iconHeight;
+        mSecondIcon.invalidate();
+    }
+
+    private boolean isExplicitUserInteractionAudioBypassAllowed() {
+        return mGlobalSettings.getInt(
+                Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) == 1;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateUI();
+        mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
+    }
+
+    @Override
+    public void onPause() {
+        mSensorPrivacyController.removeCallback(mSensorPrivacyCallback);
+        super.onPause();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index d543eb2..1b9657f 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -16,17 +16,23 @@
 
 package com.android.systemui.sensorprivacy.television;
 
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 import static android.hardware.SensorPrivacyManager.Sources.OTHER;
 
 import android.annotation.DimenRes;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
 import android.hardware.SensorPrivacyManager;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
@@ -39,6 +45,8 @@
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.tv.TvBottomSheetActivity;
 
+import java.util.List;
+
 import javax.inject.Inject;
 
 /**
@@ -57,6 +65,8 @@
 
     private int mSensor = -1;
 
+    private final AppOpsManager mAppOpsManager;
+    private final RoleManager mRoleManager;
     private final IndividualSensorPrivacyController mSensorPrivacyController;
     private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
     private TextView mTitle;
@@ -68,8 +78,11 @@
 
     @Inject
     public TvUnblockSensorActivity(
-            IndividualSensorPrivacyController individualSensorPrivacyController) {
+            IndividualSensorPrivacyController individualSensorPrivacyController,
+            AppOpsManager appOpsManager, RoleManager roleManager) {
         mSensorPrivacyController = individualSensorPrivacyController;
+        mAppOpsManager = appOpsManager;
+        mRoleManager = roleManager;
     }
 
     @Override
@@ -120,6 +133,10 @@
                 toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content;
                 break;
         }
+        showToastAndFinish(toastMsgResId);
+    }
+
+    private void showToastAndFinish(int toastMsgResId) {
         Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show();
         finish();
     }
@@ -149,7 +166,9 @@
     }
 
     private void updateUI() {
-        if (isBlockedByHardwareToggle()) {
+        if (isHTTAccessDisabled()) {
+            updateUiForHTT();
+        } else if (isBlockedByHardwareToggle()) {
             updateUiForHardwareToggle();
         } else {
             updateUiForSoftwareToggle();
@@ -208,20 +227,20 @@
 
         switch (mSensor) {
             case MICROPHONE:
-                mTitle.setText(R.string.sensor_privacy_start_use_mic_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_mic_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
                 mSecondIcon.setVisibility(View.GONE);
                 break;
             case CAMERA:
-                mTitle.setText(R.string.sensor_privacy_start_use_camera_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_camera_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
                 mSecondIcon.setVisibility(View.GONE);
                 break;
             case ALL_SENSORS:
             default:
-                mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
                 mSecondIcon.setImageResource(
@@ -241,6 +260,29 @@
         });
     }
 
+    private void updateUiForHTT() {
+        setIconTint(true);
+        setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
+
+        mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
+        mContent.setText(R.string.sensor_privacy_htt_blocked_dialog_content);
+        mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
+        mSecondIcon.setVisibility(View.GONE);
+
+        mPositiveButton.setText(R.string.sensor_privacy_dialog_open_settings);
+        mPositiveButton.setOnClickListener(v -> {
+            Intent openPrivacySettings = new Intent(ACTION_MANAGE_MICROPHONE_PRIVACY);
+            ActivityInfo activityInfo = openPrivacySettings.resolveActivityInfo(getPackageManager(),
+                    MATCH_SYSTEM_ONLY);
+            if (activityInfo == null) {
+                showToastAndFinish(com.android.internal.R.string.noApplications);
+            } else {
+                startActivity(openPrivacySettings);
+                finish();
+            }
+        });
+    }
+
     private void setIconTint(boolean enableTint) {
         final Resources resources = getResources();
 
@@ -272,6 +314,18 @@
         mSecondIcon.invalidate();
     }
 
+    private boolean isHTTAccessDisabled() {
+        String pkg = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        List<String> assistantPkgs = mRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT);
+        if (!assistantPkgs.contains(pkg)) {
+            return false;
+        }
+
+        return (mAppOpsManager.checkOpNoThrow(
+                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, UserHandle.myUserId(),
+                pkg) != AppOpsManager.MODE_ALLOWED);
+    }
+
     @Override
     public void onResume() {
         super.onResume();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 2450197..3a624ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -531,7 +531,7 @@
     private final NavigationBarController mNavigationBarController;
     private final int mDisplayId;
 
-    private KeyguardIndicationController mKeyguardIndicationController;
+    private final KeyguardIndicationController mKeyguardIndicationController;
     private int mHeadsUpInset;
     private boolean mHeadsUpPinnedMode;
     private boolean mAllowExpandForSmallExpansion;
@@ -743,6 +743,7 @@
             SysUiState sysUiState,
             Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            KeyguardIndicationController keyguardIndicationController,
             NotificationListContainer notificationListContainer,
             NotificationStackSizeCalculator notificationStackSizeCalculator,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@@ -779,6 +780,7 @@
 
         mResources = mView.getResources();
         mKeyguardStateController = keyguardStateController;
+        mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
         mNotificationShadeWindowController = notificationShadeWindowController;
         FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get();
@@ -1020,7 +1022,7 @@
         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
                 mOnEmptySpaceClickListener);
         addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
-        mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+        setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
 
         initBottomArea();
 
@@ -1264,7 +1266,7 @@
         int index = mView.indexOfChild(mKeyguardBottomArea);
         mView.removeView(mKeyguardBottomArea);
         KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
-        mKeyguardBottomArea = mKeyguardBottomAreaViewControllerProvider.get().getView();
+        setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
         mKeyguardBottomArea.initFrom(oldBottomArea);
         mView.addView(mKeyguardBottomArea, index);
         initBottomArea();
@@ -1343,8 +1345,8 @@
         return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
-    public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
-        mKeyguardIndicationController = indicationController;
+    private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
+        mKeyguardBottomArea = keyguardBottomArea;
         mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
     }
 
@@ -3880,6 +3882,7 @@
 
     public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
         mHeadsUpManager = headsUpManager;
+        mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
                 mNotificationStackScrollLayoutController.getHeadsUpCallback(),
                 NotificationPanelViewController.this);
@@ -4361,10 +4364,6 @@
         mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
     }
 
-    public ShadeHeadsUpChangedListener getOnHeadsUpChangedListener() {
-        return mOnHeadsUpChangedListener;
-    }
-
     public void setHeaderDebugInfo(String text) {
         if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
     }
@@ -4644,7 +4643,7 @@
                 mUpdateFlingVelocity = vel;
             }
         } else if (!mCentralSurfaces.isBouncerShowing()
-                && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+                && !mStatusBarKeyguardViewManager.isShowingAlternateAuth()
                 && !mKeyguardStateController.isKeyguardGoingAway()) {
             onEmptySpaceClick();
             onTrackingStopped(true);
@@ -5682,6 +5681,7 @@
 
         /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
         public boolean onInterceptTouchEvent(MotionEvent event) {
+            mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
             if (SPEW_LOGCAT) {
                 Log.v(TAG,
                         "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
@@ -5694,6 +5694,8 @@
             // Do not let touches go to shade or QS if the bouncer is visible,
             // but still let user swipe down to expand the panel, dismissing the bouncer.
             if (mCentralSurfaces.isBouncerShowing()) {
+                mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+                        + "bouncer is showing");
                 return true;
             }
             if (mCommandQueue.panelsEnabled()
@@ -5701,15 +5703,21 @@
                     && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
                 mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
                 mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+                mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+                        + "HeadsUpTouchHelper");
                 return true;
             }
             if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
                     && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+                mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+                        + "PulseExpansionHandler");
                 return true;
             }
 
             if (!isFullyCollapsed() && onQsIntercept(event)) {
                 debugLog("onQsIntercept true");
+                mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+                        + "QsIntercept");
                 return true;
             }
             if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
@@ -5740,6 +5748,9 @@
                     if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
                         cancelHeightAnimator();
                         mTouchSlopExceeded = true;
+                        mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:"
+                                + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:"
+                                + " false");
                         return true;
                     }
                     mInitialExpandY = y;
@@ -5784,6 +5795,8 @@
                                 && hAbs > Math.abs(x - mInitialExpandX)) {
                             cancelHeightAnimator();
                             startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+                            mShadeLog.v("NotificationPanelViewController MotionEvent"
+                                    + " intercepted: startExpandMotion");
                             return true;
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 65bd58d..1e63b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -284,7 +284,7 @@
                     return true;
                 }
 
-                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
                     // capture all touches if the alt auth bouncer is showing
                     return true;
                 }
@@ -322,7 +322,7 @@
                     handled = !mService.isPulsing();
                 }
 
-                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
                     // eat the touch
                     handled = true;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index 084b7dc..bf622c9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -52,6 +52,7 @@
         private val centralSurfaces: CentralSurfaces,
         private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
         private val statusBarStateController: StatusBarStateController,
+        private val shadeLogger: ShadeLogger,
         tunerService: TunerService,
         dumpManager: DumpManager
 ) : GestureDetector.SimpleOnGestureListener(), Dumpable {
@@ -77,18 +78,23 @@
     }
 
     override fun onSingleTapUp(e: MotionEvent): Boolean {
-        if (statusBarStateController.isDozing &&
-                singleTapEnabled &&
-                !dockManager.isDocked &&
-                !falsingManager.isProximityNear &&
-                !falsingManager.isFalseTap(LOW_PENALTY)
-        ) {
-            centralSurfaces.wakeUpIfDozing(
+        val isNotDocked = !dockManager.isDocked
+        shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked)
+        if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) {
+            val proximityIsNotNear = !falsingManager.isProximityNear
+            val isNotAFalseTap = !falsingManager.isFalseTap(LOW_PENALTY)
+            shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
+            if (proximityIsNotNear && isNotAFalseTap) {
+                shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
+                centralSurfaces.wakeUpIfDozing(
                     SystemClock.uptimeMillis(),
                     notificationShadeWindowView,
-                    "PULSING_SINGLE_TAP")
+                    "PULSING_SINGLE_TAP"
+                )
+            }
             return true
         }
+        shadeLogger.d("onSingleTapUp event ignored")
         return false
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index f389dd9..eaf7fae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -224,6 +224,6 @@
     }
 
     private NotificationPanelViewController getNotificationPanelViewController() {
-        return getCentralSurfaces().getPanelController();
+        return getCentralSurfaces().getNotificationPanelViewController();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 7f1bba3..7615301 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -16,6 +16,10 @@
         buffer.log(TAG, LogLevel.VERBOSE, msg)
     }
 
+    fun d(@CompileTimeConstant msg: String) {
+        buffer.log(TAG, LogLevel.DEBUG, msg)
+    }
+
     private inline fun log(
         logLevel: LogLevel,
         initializer: LogMessage.() -> Unit,
@@ -123,4 +127,25 @@
                     "animatingQs=$long1"
         })
     }
+
+    fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) {
+        log(LogLevel.DEBUG, {
+            bool1 = isDozing
+            bool2 = singleTapEnabled
+            bool3 = isNotDocked
+        }, {
+            "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+              "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3"
+        })
+    }
+
+    fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) {
+        log(LogLevel.DEBUG, {
+            bool1 = proximityIsNotNear
+            bool2 = isNotFalseTap
+        }, {
+            "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+                    "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2"
+        })
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 4ae0f6a..f786ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -39,7 +39,7 @@
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.inputmethodservice.InputMethodService.BackDispositionMode;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
@@ -53,7 +53,7 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -154,7 +154,7 @@
     //TODO(b/169175022) Update name and when feature name is locked.
     private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE      = 58 << MSG_SHIFT;
     private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT;
-    private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
+    private static final int MSG_SET_UDFPS_REFRESH_RATE_CALLBACK = 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;
@@ -333,9 +333,9 @@
         }
 
         /**
-         * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+         * @see IStatusBar#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)
          */
-        default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+        default void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
         }
 
         /**
@@ -356,11 +356,11 @@
         default void onRecentsAnimationStateChanged(boolean running) { }
 
         /**
-         * @see IStatusBar#onSystemBarAttributesChanged.
+         * @see IStatusBar#onSystemBarAttributesChanged
          */
         default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) { }
 
         /**
@@ -1017,9 +1017,9 @@
     }
 
     @Override
-    public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+    public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
+            mHandler.obtainMessage(MSG_SET_UDFPS_REFRESH_RATE_CALLBACK, callback).sendToTarget();
         }
     }
 
@@ -1090,7 +1090,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
@@ -1099,7 +1099,7 @@
             args.argi3 = navbarColorManagedByIme ? 1 : 0;
             args.arg1 = appearanceRegions;
             args.argi4 = behavior;
-            args.arg2 = requestedVisibilities;
+            args.argi5 = requestedVisibleTypes;
             args.arg3 = packageName;
             args.arg4 = letterboxDetails;
             mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
@@ -1546,9 +1546,10 @@
                                 (IBiometricContextListener) msg.obj);
                     }
                     break;
-                case MSG_SET_UDFPS_HBM_LISTENER:
+                case MSG_SET_UDFPS_REFRESH_RATE_CALLBACK:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
+                        mCallbacks.get(i).setUdfpsRefreshRateCallback(
+                                (IUdfpsRefreshRateRequestCallback) msg.obj);
                     }
                     break;
                 case MSG_SHOW_CHARGING_ANIMATION:
@@ -1581,8 +1582,7 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
                                 (AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
-                                (InsetsVisibilities) args.arg2, (String) args.arg3,
-                                (LetterboxDetails[]) args.arg4);
+                                args.argi5, (String) args.arg3, (LetterboxDetails[]) args.arg4);
                     }
                     args.recycle();
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 87ef92a..408293c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -496,9 +496,6 @@
             return;
         }
 
-        final float smallCornerRadius =
-                getResources().getDimension(R.dimen.notification_corner_radius_small)
-                /  getResources().getDimension(R.dimen.notification_corner_radius);
         final float viewEnd = viewStart + anv.getActualHeight();
         final float cornerAnimationDistance = mCornerAnimationDistance
                 * mAmbientState.getExpansionFraction();
@@ -509,7 +506,7 @@
             final float changeFraction = MathUtils.saturate(
                     (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
             anv.requestBottomRoundness(
-                    anv.isLastInSection() ? 1f : changeFraction,
+                    /* value = */ anv.isLastInSection() ? 1f : changeFraction,
                     /* animate = */ false,
                     SourceType.OnScroll);
 
@@ -517,7 +514,7 @@
             // Fast scroll skips frames and leaves corners with unfinished rounding.
             // Reset top and bottom corners outside of animation bounds.
             anv.requestBottomRoundness(
-                    anv.isLastInSection() ? 1f : smallCornerRadius,
+                    /* value = */ anv.isLastInSection() ? 1f : 0f,
                     /* animate = */ false,
                     SourceType.OnScroll);
         }
@@ -527,16 +524,16 @@
             final float changeFraction = MathUtils.saturate(
                     (viewStart - cornerAnimationTop) / cornerAnimationDistance);
             anv.requestTopRoundness(
-                    anv.isFirstInSection() ? 1f : changeFraction,
-                    false,
+                    /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
+                    /* animate = */ false,
                     SourceType.OnScroll);
 
         } else if (viewStart < cornerAnimationTop) {
             // Fast scroll skips frames and leaves corners with unfinished rounding.
             // Reset top and bottom corners outside of animation bounds.
             anv.requestTopRoundness(
-                    anv.isFirstInSection() ? 1f : smallCornerRadius,
-                    false,
+                    /* value = */ anv.isFirstInSection() ? 1f : 0f,
+                    /* animate = */ false,
                     SourceType.OnScroll);
         }
     }
@@ -976,6 +973,16 @@
         mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
     }
 
+    /**
+     * This method resets the OnScroll roundness of a view to 0f
+     *
+     * Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
+     */
+    public static void resetOnScrollRoundness(ExpandableView expandableView) {
+        expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
+        expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+    }
+
     public class ShelfState extends ExpandableViewState {
         private boolean hasItemsInStableShelf;
         private ExpandableView firstViewInShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index c04bc82..fdec745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar;
 
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
@@ -34,9 +32,10 @@
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InsetsFlags;
-import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.animation.Interpolator;
@@ -497,9 +496,9 @@
 
     @Override
     public void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName) {
-        boolean isFullscreen = !requestedVisibilities.getVisibility(ITYPE_STATUS_BAR)
-                || !requestedVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
+            @InsetsType int requestedVisibleTypes, String packageName) {
+        boolean isFullscreen = (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0
+                || (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0;
         if (mIsFullscreen != isFullscreen) {
             mIsFullscreen = isFullscreen;
             synchronized (mListeners) {
@@ -514,12 +513,12 @@
         if (DEBUG_IMMERSIVE_APPS) {
             boolean dim = (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0;
             String behaviorName = ViewDebug.flagsToString(InsetsFlags.class, "behavior", behavior);
-            String requestedVisibilityString = requestedVisibilities.toString();
-            if (requestedVisibilityString.isEmpty()) {
-                requestedVisibilityString = "none";
+            String requestedVisibleTypesString = WindowInsets.Type.toString(requestedVisibleTypes);
+            if (requestedVisibleTypesString.isEmpty()) {
+                requestedVisibleTypesString = "none";
             }
             Log.d(TAG, packageName + " dim=" + dim + " behavior=" + behaviorName
-                    + " requested visibilities: " + requestedVisibilityString);
+                    + " requested visible types: " + requestedVisibleTypesString);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 2cc7738..1189107 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,8 +19,8 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -154,7 +154,7 @@
      * Set the system bar attributes
      */
     void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName);
+            @InsetsType int requestedVisibleTypes, String packageName);
 
     /**
      * Set pulsing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 26f0ad9..0554fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -44,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationGroupingUtil;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -308,6 +309,11 @@
 
         row.setContentTransformationAmount(0, false /* isLastChild */);
         row.setNotificationFaded(mContainingNotificationIsFaded);
+
+        // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
+        // Here we should reset the `OnScroll` roundness only on top-level rows.
+        NotificationShelf.resetOnScrollRoundness(row);
+
         // It doesn't make sense to keep old animations around, lets cancel them!
         ExpandableViewState viewState = row.getViewState();
         if (viewState != null) {
@@ -1377,8 +1383,12 @@
             if (child.getVisibility() == View.GONE) {
                 continue;
             }
+            child.requestTopRoundness(
+                    /* value = */ 0f,
+                    /* animate = */ isShown(),
+                    SourceType.DefaultValue);
             child.requestBottomRoundness(
-                    last ? getBottomRoundness() : 0f,
+                    /* value = */ last ? getBottomRoundness() : 0f,
                     /* animate = */ isShown(),
                     SourceType.DefaultValue);
             last = false;
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 c4ef28e..2c3330e 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
@@ -114,6 +114,8 @@
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.LargeScreenUtils;
 
+import com.google.errorprone.annotations.CompileTimeConstant;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
@@ -3693,6 +3695,8 @@
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
     void handleEmptySpaceClick(MotionEvent ev) {
+        logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY),
+                mStatusBarState, mTouchIsClick);
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_MOVE:
                 final float touchSlop = getTouchSlop(ev);
@@ -3704,12 +3708,34 @@
             case MotionEvent.ACTION_UP:
                 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
+                    debugLog("handleEmptySpaceClick: touch event propagated further");
                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
                 }
                 break;
+            default:
+                debugLog("handleEmptySpaceClick: MotionEvent ignored");
         }
     }
 
+    private void debugLog(@CompileTimeConstant String s) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.d(s);
+    }
+
+    private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
+            int statusBarState, boolean touchIsClick) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.logEmptySpaceClick(
+                isTouchBelowLastNotification,
+                statusBarState,
+                touchIsClick,
+                MotionEvent.actionToString(ev.getActionMasked()));
+    }
+
     @ShadeViewRefactor(RefactorComponent.INPUT)
     void initDownStates(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 4c52db7..64dd6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -2,6 +2,7 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
@@ -10,6 +11,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
+import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
 class NotificationStackScrollLogger @Inject constructor(
@@ -56,6 +58,25 @@
                     "key: $str1 expected: $bool1 actual: $bool2"
         })
     }
+
+    fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+
+    fun logEmptySpaceClick(
+        isBelowLastNotification: Boolean,
+        statusBarState: Int,
+        touchIsClick: Boolean,
+        motionEventDesc: String
+    ) {
+        buffer.log(TAG, DEBUG, {
+            int1 = statusBarState
+            bool1 = touchIsClick
+            bool2 = isBelowLastNotification
+            str1 = motionEventDesc
+        }, {
+            "handleEmptySpaceClick: statusBarState: $int1 isTouchAClick: $bool1 " +
+                    "isTouchBelowNotification: $bool2 motionEvent: $str1"
+        })
+    }
 }
 
 private const val TAG = "NotificationStackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 9900e41..2f7d344 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -64,8 +64,10 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -163,7 +165,7 @@
     private PendingAuthenticated mPendingAuthenticated = null;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
-    private BiometricModeListener mBiometricModeListener;
+    private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>();
 
     private final MetricsLogger mMetricsLogger;
     private final AuthController mAuthController;
@@ -305,9 +307,14 @@
         mKeyguardViewController = keyguardViewController;
     }
 
-    /** Sets a {@link BiometricModeListener}. */
-    public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
-        mBiometricModeListener = biometricModeListener;
+    /** Adds a {@link BiometricModeListener}. */
+    public void addBiometricModeListener(BiometricModeListener listener) {
+        mBiometricModeListeners.add(listener);
+    }
+
+    /** Removes a {@link BiometricModeListener}. */
+    public void removeBiometricModeListener(BiometricModeListener listener) {
+        mBiometricModeListeners.remove(listener);
     }
 
     private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -481,15 +488,12 @@
                 break;
         }
         onModeChanged(mMode);
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.notifyBiometricAuthModeChanged();
-        }
         Trace.endSection();
     }
 
     private void onModeChanged(@WakeAndUnlockMode int mode) {
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.onModeChanged(mode);
+        for (BiometricModeListener listener : mBiometricModeListeners) {
+            listener.onModeChanged(mode);
         }
     }
 
@@ -696,9 +700,8 @@
         mMode = MODE_NONE;
         mBiometricType = null;
         mNotificationShadeWindowController.setForceDozeBrightness(false);
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.onResetMode();
-            mBiometricModeListener.notifyBiometricAuthModeChanged();
+        for (BiometricModeListener listener : mBiometricModeListeners) {
+            listener.onResetMode();
         }
         mNumConsecutiveFpFailures = 0;
         mLastFpFailureUptimeMillis = 0;
@@ -807,10 +810,8 @@
     /** An interface to interact with the {@link BiometricUnlockController}. */
     public interface BiometricModeListener {
         /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
-        void onResetMode();
+        default void onResetMode() {}
         /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
-        void onModeChanged(@WakeAndUnlockMode int mode);
-        /** Called after processing {@link #onModeChanged(int)}. */
-        void notifyBiometricAuthModeChanged();
+        default void onModeChanged(@WakeAndUnlockMode int mode) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 2504fc1..1961e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -196,8 +196,6 @@
 
     void collapsePanelOnMainThread();
 
-    void collapsePanelWithDuration(int duration);
-
     void togglePanel();
 
     void start();
@@ -305,9 +303,6 @@
 
     void checkBarModes();
 
-    // Called by NavigationBarFragment
-    void setQsScrimEnabled(boolean scrimEnabled);
-
     void updateBubblesVisibility();
 
     void setInteracting(int barWindow, boolean interacting);
@@ -379,8 +374,6 @@
 
     void showKeyguardImpl();
 
-    boolean isInLaunchTransition();
-
     void fadeKeyguardAfterLaunchTransition(Runnable beforeFading,
             Runnable endRunnable, Runnable cancelRunnable);
 
@@ -437,8 +430,6 @@
 
     void showPinningEscapeToast();
 
-    KeyguardBottomAreaView getKeyguardBottomAreaView();
-
     void setBouncerShowing(boolean bouncerShowing);
 
     void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
@@ -505,12 +496,8 @@
 
     boolean isBouncerShowingOverDream();
 
-    void onBouncerPreHideAnimation();
-
     boolean isKeyguardSecure();
 
-    NotificationPanelViewController getPanelController();
-
     NotificationGutsManager getGutsManager();
 
     void updateNotificationPanelTouchState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index d6fadca..41f0520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -38,8 +38,8 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -458,7 +458,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         if (displayId != mDisplayId) {
             return;
@@ -471,7 +471,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails
         );
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index eb7a742..6bb5272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -466,7 +466,7 @@
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
     private final PluginManager mPluginManager;
-    private final com.android.systemui.shade.ShadeController mShadeController;
+    private final ShadeController mShadeController;
     private final InitController mInitController;
 
     private final PluginDependencyProvider mPluginDependencyProvider;
@@ -479,9 +479,9 @@
     private final StatusBarSignalPolicy mStatusBarSignalPolicy;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
 
-    // expanded notifications
-    // the sliding/resizing panel within the notification window
-    protected NotificationPanelViewController mNotificationPanelViewController;
+    /** Controller for the Shade. */
+    @VisibleForTesting
+    NotificationPanelViewController mNotificationPanelViewController;
 
     // settings
     private QSPanelController mQSPanelController;
@@ -928,7 +928,7 @@
         }
         mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
                 result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
-                result.mRequestedVisibilities, result.mPackageName, result.mLetterboxDetails);
+                result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
 
         // StatusBarManagerService has a back up of IME token and it's restored here.
         mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken,
@@ -1152,7 +1152,6 @@
         initializer.initializeStatusBar(mCentralSurfacesComponent);
 
         mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
-        mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
 
         createNavigationBar(result);
@@ -1161,9 +1160,6 @@
             mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
         }
 
-        mNotificationPanelViewController.setKeyguardIndicationController(
-                mKeyguardIndicationController);
-
         mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
                 R.id.ambient_indication_container);
 
@@ -1514,11 +1510,12 @@
     protected void startKeyguard() {
         Trace.beginSection("CentralSurfaces#startKeyguard");
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
-        mBiometricUnlockController.setBiometricModeListener(
+        mBiometricUnlockController.addBiometricModeListener(
                 new BiometricUnlockController.BiometricModeListener() {
                     @Override
                     public void onResetMode() {
                         setWakeAndUnlocking(false);
+                        notifyBiometricAuthModeChanged();
                     }
 
                     @Override
@@ -1529,11 +1526,7 @@
                             case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
                                 setWakeAndUnlocking(true);
                         }
-                    }
-
-                    @Override
-                    public void notifyBiometricAuthModeChanged() {
-                        CentralSurfacesImpl.this.notifyBiometricAuthModeChanged();
+                        notifyBiometricAuthModeChanged();
                     }
 
                     private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
@@ -2001,8 +1994,7 @@
     }
 
     void makeExpandedInvisible() {
-        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
-                + " mExpandedVisible=" + mExpandedVisible);
+        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
 
         if (!mExpandedVisible || mNotificationShadeWindowView == null) {
             return;
@@ -2178,12 +2170,6 @@
         mNoAnimationOnNextBarModeChange = false;
     }
 
-    // Called by NavigationBarFragment
-    @Override
-    public void setQsScrimEnabled(boolean scrimEnabled) {
-        mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
-    }
-
     /** Temporarily hides Bubbles if the status bar is hidden. */
     @Override
     public void updateBubblesVisibility() {
@@ -2559,14 +2545,11 @@
                                 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
                                 true /* force */, true /* delayed*/);
                     } else {
-
                         // Do it after DismissAction has been processed to conserve the needed
                         // ordering.
                         mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
                     }
-                } else if (CentralSurfacesImpl.this.isInLaunchTransition()
-                        && mNotificationPanelViewController.isLaunchTransitionFinished()) {
-
+                } else if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
                     // We are not dismissing the shade, but the launch transition is already
                     // finished,
                     // so nobody will call readyForKeyguardDone anymore. Post it such that
@@ -2988,11 +2971,6 @@
         mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
-    @Override
-    public boolean isInLaunchTransition() {
-        return mNotificationPanelViewController.isLaunchTransitionFinished();
-    }
-
     /**
      * Fades the content of the keyguard away after the launch transition is done.
      *
@@ -3373,12 +3351,6 @@
         }
     }
 
-    /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */
-    @Override
-    public void collapsePanelWithDuration(int duration) {
-        mNotificationPanelViewController.collapseWithDuration(duration);
-    }
-
     /**
      * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
      * from the power button).
@@ -3468,12 +3440,6 @@
         mNavigationBarController.showPinningEscapeToast(mDisplayId);
     }
 
-    //TODO(b/254875405): this should be removed.
-    @Override
-    public KeyguardBottomAreaView getKeyguardBottomAreaView() {
-        return mNotificationPanelViewController.getKeyguardBottomAreaView();
-    }
-
     protected ViewRootImpl getViewRootImpl()  {
         NotificationShadeWindowView nswv = getNotificationShadeWindowView();
         if (nswv != null) return nswv.getViewRootImpl();
@@ -4176,23 +4142,11 @@
         return mBouncerShowingOverDream;
     }
 
-    /**
-     * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
-     */
-    @Override
-    public void onBouncerPreHideAnimation() {
-        mNotificationPanelViewController.startBouncerPreHideAnimation();
-
-    }
-
     @Override
     public boolean isKeyguardSecure() {
         return mStatusBarKeyguardViewManager.isSecure();
     }
-    @Override
-    public NotificationPanelViewController getPanelController() {
-        return mNotificationPanelViewController;
-    }
+
     // End Extra BaseStatusBarMethods.
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 37f04bb..a28fca1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -64,6 +64,11 @@
     private static final String TAG = "KeyguardBouncer";
     static final long BOUNCER_FACE_DELAY = 1200;
     public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
+    /**
+     * Values for the bouncer expansion represented as the panel expansion.
+     * Panel expansion 1f = panel fully showing = bouncer fully hidden
+     * Panel expansion 0f = panel fully hiding = bouncer fully showing
+     */
     public static final float EXPANSION_HIDDEN = 1f;
     public static final float EXPANSION_VISIBLE = 0f;
 
@@ -143,6 +148,14 @@
     }
 
     /**
+     * Get the KeyguardBouncer expansion
+     * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition.
+     */
+    public float getExpansion() {
+        return mExpansion;
+    }
+
+    /**
      * Enable/disable only the back button
      */
     public void setBackButtonEnabled(boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 6e98c49..eba7fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -22,8 +22,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -144,7 +144,7 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) {
             if (displayId != mDisplayId) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5480f5d..93b6437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -86,8 +86,10 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -166,13 +168,9 @@
 
         @Override
         public void onExpansionChanged(float expansion) {
-            if (mAlternateAuthInterceptor != null) {
-                mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion);
-            }
             if (mBouncerAnimating) {
                 mCentralSurfaces.setBouncerHiddenFraction(expansion);
             }
-            updateStates();
         }
 
         @Override
@@ -184,9 +182,6 @@
             if (!isVisible) {
                 mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
             }
-            if (mAlternateAuthInterceptor != null) {
-                mAlternateAuthInterceptor.onBouncerVisibilityChanged();
-            }
 
             /* Register predictive back callback when keyguard becomes visible, and unregister
             when it's hidden. */
@@ -252,6 +247,7 @@
     private int mLastBiometricMode;
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
+    final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsModernBouncerEnabled;
 
     private OnDismissAction mAfterKeyguardGoneAction;
@@ -465,7 +461,7 @@
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
         } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
             // Don't expand to the bouncer. Instead transition back to the lock screen (see
@@ -475,17 +471,17 @@
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
             }
         } else if (mKeyguardStateController.isShowing()  && !hideBouncerOverDream) {
             if (!isWakeAndUnlocking()
                     && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
-                    && !mCentralSurfaces.isInLaunchTransition()
+                    && !mNotificationPanelViewController.isLaunchTransitionFinished()
                     && !isUnlockCollapsing()) {
                 if (mBouncer != null) {
                     mBouncer.setExpansion(fraction);
                 } else {
-                    mBouncerInteractor.setExpansion(fraction);
+                    mBouncerInteractor.setPanelExpansion(fraction);
                 }
             }
             if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
@@ -504,7 +500,7 @@
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
         } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
             // Panel expanded while pulsing but didn't translate the bouncer (because we are
@@ -849,7 +845,7 @@
         if (isShowing && isOccluding) {
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
-            if (mCentralSurfaces.isInLaunchTransition()) {
+            if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
                 final Runnable endRunnable = new Runnable() {
                     @Override
                     public void run() {
@@ -903,7 +899,7 @@
             } else {
                 mBouncerInteractor.startDisappearAnimation(finishRunnable);
             }
-            mCentralSurfaces.onBouncerPreHideAnimation();
+            mNotificationPanelViewController.startBouncerPreHideAnimation();
 
             // We update the state (which will show the keyguard) only if an animation will run on
             // the keyguard. If there is no animation, we wait before updating the state so that we
@@ -935,7 +931,7 @@
         long uptimeMillis = SystemClock.uptimeMillis();
         long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
 
-        if (mCentralSurfaces.isInLaunchTransition()
+        if (mNotificationPanelViewController.isLaunchTransitionFinished()
                 || mKeyguardStateController.isFlingingToDismissKeyguard()) {
             final boolean wasFlingingToDismissKeyguard =
                     mKeyguardStateController.isFlingingToDismissKeyguard();
@@ -1312,7 +1308,7 @@
 
     @Override
     public boolean shouldDisableWindowAnimationsForUnlock() {
-        return mCentralSurfaces.isInLaunchTransition();
+        return mNotificationPanelViewController.isLaunchTransitionFinished();
     }
 
     @Override
@@ -1355,7 +1351,7 @@
             mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
         }
 
-        if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
+        if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) {
             resetAlternateAuth(false);
             executeAfterKeyguardGoneAction();
         }
@@ -1441,6 +1437,10 @@
         pw.println("  mPendingWakeupAction: " + mPendingWakeupAction);
         pw.println("  isBouncerShowing(): " + isBouncerShowing());
         pw.println("  bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
+        pw.println("  Registered KeyguardViewManagerCallbacks:");
+        for (KeyguardViewManagerCallback callback : mCallbacks) {
+            pw.println("      " + callback);
+        }
 
         if (mBouncer != null) {
             mBouncer.dump(pw);
@@ -1465,6 +1465,20 @@
     }
 
     /**
+     * Add a callback to listen for changes
+     */
+    public void addCallback(KeyguardViewManagerCallback callback) {
+        mCallbacks.add(callback);
+    }
+
+    /**
+     * Removes callback to stop receiving updates
+     */
+    public void removeCallback(KeyguardViewManagerCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /**
      * Whether qs is currently expanded.
      */
     public float getQsExpansion() {
@@ -1476,8 +1490,8 @@
      */
     public void setQsExpansion(float qsExpansion) {
         mQsExpansion = qsExpansion;
-        if (mAlternateAuthInterceptor != null) {
-            mAlternateAuthInterceptor.setQsExpansion(qsExpansion);
+        for (KeyguardViewManagerCallback callback : mCallbacks) {
+            callback.onQSExpansionChanged(mQsExpansion);
         }
     }
 
@@ -1491,21 +1505,13 @@
                 && mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
     }
 
-    public boolean isShowingAlternateAuthOrAnimating() {
-        return mAlternateAuthInterceptor != null
-                && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()
-                || mAlternateAuthInterceptor.isAnimating());
-    }
-
     /**
-     * Forward touches to any alternate authentication affordances.
+     * Forward touches to callbacks.
      */
-    public boolean onTouch(MotionEvent event) {
-        if (mAlternateAuthInterceptor == null) {
-            return false;
+    public void onTouch(MotionEvent event) {
+        for (KeyguardViewManagerCallback callback: mCallbacks) {
+            callback.onTouch(event);
         }
-
-        return mAlternateAuthInterceptor.onTouch(event);
     }
 
     /** Update keyguard position based on a tapped X coordinate. */
@@ -1639,38 +1645,6 @@
         boolean isShowingAlternateAuthBouncer();
 
         /**
-         * print information for the alternate auth interceptor registered
-         */
-        void dump(PrintWriter pw);
-
-        /**
-         * @return true if the new auth method bouncer is currently animating in or out.
-         */
-        boolean isAnimating();
-
-        /**
-         * How much QS is fully expanded where 0f is not showing and 1f is fully expanded.
-         */
-        void setQsExpansion(float qsExpansion);
-
-        /**
-         * Forward potential touches to authentication interceptor
-         * @return true if event was handled
-         */
-        boolean onTouch(MotionEvent event);
-
-        /**
-         * Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully
-         * hidden
-         */
-        void setBouncerExpansionChanged(float expansion);
-
-        /**
-         *  called when the bouncer view visibility has changed.
-         */
-        void onBouncerVisibilityChanged();
-
-        /**
          * Use when an app occluding the keyguard would like to give the user ability to
          * unlock the device using udfps.
          *
@@ -1679,5 +1653,25 @@
          */
         void requestUdfps(boolean requestUdfps, int color);
 
+        /**
+         * print information for the alternate auth interceptor registered
+         */
+        void dump(PrintWriter pw);
+    }
+
+    /**
+     * Callback for KeyguardViewManager state changes.
+     */
+    public interface KeyguardViewManagerCallback {
+        /**
+         * Set the amount qs is expanded. For example, swipe down from the top of the
+         * lock screen to start the full QS expansion.
+         */
+        default void onQSExpansionChanged(float qsExpansion) { }
+
+        /**
+         * Forward touch events to callbacks
+         */
+        default void onTouch(MotionEvent event) { }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index ee948c0..b1642d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -31,7 +31,7 @@
         delegate.onLaunchAnimationStart(isExpandingFullyAbove)
         centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true)
         if (!isExpandingFullyAbove) {
-            centralSurfaces.collapsePanelWithDuration(
+            centralSurfaces.notificationPanelViewController.collapseWithDuration(
                 ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index 6cd8c78..08599c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.phone
 
-import android.view.InsetsVisibilities
+import android.view.WindowInsets.Type.InsetsType
 import android.view.WindowInsetsController.Appearance
 import android.view.WindowInsetsController.Behavior
 import com.android.internal.statusbar.LetterboxDetails
@@ -66,21 +66,21 @@
                 params.appearanceRegionsArray,
                 params.navbarColorManagedByIme,
                 params.behavior,
-                params.requestedVisibilities,
+                params.requestedVisibleTypes,
                 params.packageName,
                 params.letterboxesArray)
         }
     }
 
     fun onSystemBarAttributesChanged(
-        displayId: Int,
-        @Appearance originalAppearance: Int,
-        originalAppearanceRegions: Array<AppearanceRegion>,
-        navbarColorManagedByIme: Boolean,
-        @Behavior behavior: Int,
-        requestedVisibilities: InsetsVisibilities,
-        packageName: String,
-        letterboxDetails: Array<LetterboxDetails>
+            displayId: Int,
+            @Appearance originalAppearance: Int,
+            originalAppearanceRegions: Array<AppearanceRegion>,
+            navbarColorManagedByIme: Boolean,
+            @Behavior behavior: Int,
+            @InsetsType requestedVisibleTypes: Int,
+            packageName: String,
+            letterboxDetails: Array<LetterboxDetails>
     ) {
         lastSystemBarAttributesParams =
             SystemBarAttributesParams(
@@ -89,7 +89,7 @@
                 originalAppearanceRegions.toList(),
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails.toList())
 
@@ -104,7 +104,7 @@
 
         centralSurfaces.updateBubblesVisibility()
         statusBarStateController.setSystemBarAttributes(
-            appearance, behavior, requestedVisibilities, packageName)
+            appearance, behavior, requestedVisibleTypes, packageName)
     }
 
     private fun modifyAppearanceIfNeeded(
@@ -137,14 +137,14 @@
  * [SystemBarAttributesListener.onSystemBarAttributesChanged].
  */
 private data class SystemBarAttributesParams(
-    val displayId: Int,
-    @Appearance val appearance: Int,
-    val appearanceRegions: List<AppearanceRegion>,
-    val navbarColorManagedByIme: Boolean,
-    @Behavior val behavior: Int,
-    val requestedVisibilities: InsetsVisibilities,
-    val packageName: String,
-    val letterboxes: List<LetterboxDetails>,
+        val displayId: Int,
+        @Appearance val appearance: Int,
+        val appearanceRegions: List<AppearanceRegion>,
+        val navbarColorManagedByIme: Boolean,
+        @Behavior val behavior: Int,
+        @InsetsType val requestedVisibleTypes: Int,
+        val packageName: String,
+        val letterboxes: List<LetterboxDetails>,
 ) {
     val letterboxesArray = letterboxes.toTypedArray()
     val appearanceRegionsArray = appearanceRegions.toTypedArray()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
new file mode 100644
index 0000000..e618905
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.net.NetworkCapabilities
+
+/** Provides information about a mobile network connection */
+data class MobileConnectivityModel(
+    /** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */
+    val isConnected: Boolean = false,
+    /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
+    val isValidated: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 06e8f46..581842b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -16,11 +16,15 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings.Global
 import android.telephony.CellSignalStrength
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
 import android.telephony.SignalStrength
 import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
@@ -34,6 +38,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.util.settings.GlobalSettings
 import java.lang.IllegalStateException
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -42,9 +47,12 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -65,14 +73,23 @@
      */
     val subscriptionModelFlow: Flow<MobileSubscriptionModel>
     /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
-    val dataEnabled: Flow<Boolean>
+    val dataEnabled: StateFlow<Boolean>
+    /**
+     * True if this connection represents the default subscription per
+     * [SubscriptionManager.getDefaultDataSubscriptionId]
+     */
+    val isDefaultDataSubscription: StateFlow<Boolean>
 }
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 class MobileConnectionRepositoryImpl(
+    private val context: Context,
     private val subId: Int,
     private val telephonyManager: TelephonyManager,
+    private val globalSettings: GlobalSettings,
+    defaultDataSubId: StateFlow<Int>,
+    globalMobileDataSettingChangedEvent: Flow<Unit>,
     bgDispatcher: CoroutineDispatcher,
     logger: ConnectivityPipelineLogger,
     scope: CoroutineScope,
@@ -86,6 +103,8 @@
         }
     }
 
+    private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
     override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
         var state = MobileSubscriptionModel()
         conflatedCallbackFlow {
@@ -165,33 +184,75 @@
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
+            .onEach { telephonyCallbackEvent.tryEmit(Unit) }
             .logOutputChange(logger, "MobileSubscriptionModel")
             .stateIn(scope, SharingStarted.WhileSubscribed(), state)
     }
 
+    /** Produces whenever the mobile data setting changes for this subId */
+    private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+        val observer =
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    trySend(Unit)
+                }
+            }
+
+        globalSettings.registerContentObserver(
+            globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
+            /* notifyForDescendants */ true,
+            observer
+        )
+
+        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+    }
+
     /**
      * There are a few cases where we will need to poll [TelephonyManager] so we can update some
      * internal state where callbacks aren't provided. Any of those events should be merged into
      * this flow, which can be used to trigger the polling.
      */
-    private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {}
+    private val telephonyPollingEvent: Flow<Unit> =
+        merge(
+            telephonyCallbackEvent,
+            localMobileDataSettingChangedEvent,
+            globalMobileDataSettingChangedEvent,
+        )
 
-    override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() }
+    override val dataEnabled: StateFlow<Boolean> =
+        telephonyPollingEvent
+            .mapLatest { dataConnectionAllowed() }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
 
     private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
 
+    override val isDefaultDataSubscription: StateFlow<Boolean> =
+        defaultDataSubId
+            .mapLatest { it == subId }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+
     class Factory
     @Inject
     constructor(
+        private val context: Context,
         private val telephonyManager: TelephonyManager,
         private val logger: ConnectivityPipelineLogger,
+        private val globalSettings: GlobalSettings,
         @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
-        fun build(subId: Int): MobileConnectionRepository {
+        fun build(
+            subId: Int,
+            defaultDataSubId: StateFlow<Int>,
+            globalMobileDataSettingChangedEvent: Flow<Unit>,
+        ): MobileConnectionRepository {
             return MobileConnectionRepositoryImpl(
+                context,
                 subId,
                 telephonyManager.createForSubscriptionId(subId),
+                globalSettings,
+                defaultDataSubId,
+                globalMobileDataSettingChangedEvent,
                 bgDispatcher,
                 logger,
                 scope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 0e2428a..c3c1f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -16,15 +16,27 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
+import android.provider.Settings.Global.MOBILE_DATA
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
 import androidx.annotation.VisibleForTesting
+import com.android.internal.telephony.PhoneConstants
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.MobileMappings.Config
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,7 +44,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -40,10 +54,12 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
@@ -57,13 +73,22 @@
     val subscriptionsFlow: Flow<List<SubscriptionInfo>>
 
     /** Observable for the subscriptionId of the current mobile data connection */
-    val activeMobileDataSubscriptionId: Flow<Int>
+    val activeMobileDataSubscriptionId: StateFlow<Int>
 
     /** Observable for [MobileMappings.Config] tracking the defaults */
     val defaultDataSubRatConfig: StateFlow<Config>
 
+    /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
+    val defaultDataSubId: StateFlow<Int>
+
+    /** The current connectivity status for the default mobile network connection */
+    val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
+
     /** Get or create a repository for the line of service for the given subscription ID */
     fun getRepoForSubId(subId: Int): MobileConnectionRepository
+
+    /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
+    val globalMobileDataSettingChangedEvent: Flow<Unit>
 }
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -72,10 +97,12 @@
 class MobileConnectionsRepositoryImpl
 @Inject
 constructor(
+    private val connectivityManager: ConnectivityManager,
     private val subscriptionManager: SubscriptionManager,
     private val telephonyManager: TelephonyManager,
     private val logger: ConnectivityPipelineLogger,
     broadcastDispatcher: BroadcastDispatcher,
+    private val globalSettings: GlobalSettings,
     private val context: Context,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
@@ -121,17 +148,26 @@
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+
+    private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
+        MutableSharedFlow(extraBufferCapacity = 1)
+
+    override val defaultDataSubId: StateFlow<Int> =
+        broadcastDispatcher
+            .broadcastFlow(
+                IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+            ) { intent, _ ->
+                intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+            }
+            .distinctUntilChanged()
+            .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
             .stateIn(
                 scope,
-                started = SharingStarted.WhileSubscribed(),
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                SharingStarted.WhileSubscribed(),
+                SubscriptionManager.getDefaultDataSubscriptionId()
             )
 
-    private val defaultDataSubChangedEvent =
-        broadcastDispatcher.broadcastFlow(
-            IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-        )
-
     private val carrierConfigChangedEvent =
         broadcastDispatcher.broadcastFlow(
             IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
@@ -148,9 +184,8 @@
      * This flow will produce whenever the default data subscription or the carrier config changes.
      */
     override val defaultDataSubRatConfig: StateFlow<Config> =
-        combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
-                Config.readConfig(context)
-            }
+        merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+            .mapLatest { Config.readConfig(context) }
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
@@ -168,6 +203,57 @@
             ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
     }
 
+    /**
+     * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
+     * connection repositories also observe the URI for [MOBILE_DATA] + subId.
+     */
+    override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+        val observer =
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    trySend(Unit)
+                }
+            }
+
+        globalSettings.registerContentObserver(
+            globalSettings.getUriFor(MOBILE_DATA),
+            true,
+            observer
+        )
+
+        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+    }
+
+    @SuppressLint("MissingPermission")
+    override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+                        override fun onLost(network: Network) {
+                            // Send a disconnected model when lost. Maybe should create a sealed
+                            // type or null here?
+                            trySend(MobileConnectivityModel())
+                        }
+
+                        override fun onCapabilitiesChanged(
+                            network: Network,
+                            caps: NetworkCapabilities
+                        ) {
+                            trySend(
+                                MobileConnectivityModel(
+                                    isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
+                                    isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
+                                )
+                            )
+                        }
+                    }
+
+                connectivityManager.registerDefaultNetworkCallback(callback)
+
+                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
+
     private fun isValidSubId(subId: Int): Boolean {
         subscriptionsFlow.value.forEach {
             if (it.subscriptionId == subId) {
@@ -181,7 +267,11 @@
     @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
 
     private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
-        return mobileConnectionRepositoryFactory.build(subId)
+        return mobileConnectionRepositoryFactory.build(
+            subId,
+            defaultDataSubId,
+            globalMobileDataSettingChangedEvent,
+        )
     }
 
     private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
index 77de849..91886bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -26,7 +26,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.mapLatest
@@ -40,7 +39,7 @@
  */
 interface UserSetupRepository {
     /** Observable tracking [DeviceProvisionedController.isUserSetup] */
-    val isUserSetupFlow: Flow<Boolean>
+    val isUserSetupFlow: StateFlow<Boolean>
 }
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index f99d278c..0da84f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -18,81 +18,109 @@
 
 import android.telephony.CarrierConfigManager
 import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 
 interface MobileIconInteractor {
+    /** Only true if mobile is the default transport but is not validated, otherwise false */
+    val isDefaultConnectionFailed: StateFlow<Boolean>
+
+    /** True when telephony tells us that the data state is CONNECTED */
+    val isDataConnected: StateFlow<Boolean>
+
+    // TODO(b/256839546): clarify naming of default vs active
+    /** True if we want to consider the data connection enabled */
+    val isDefaultDataEnabled: StateFlow<Boolean>
+
     /** Observable for the data enabled state of this connection */
-    val isDataEnabled: Flow<Boolean>
+    val isDataEnabled: StateFlow<Boolean>
 
     /** Observable for RAT type (network type) indicator */
-    val networkTypeIconGroup: Flow<MobileIconGroup>
+    val networkTypeIconGroup: StateFlow<MobileIconGroup>
 
     /** True if this line of service is emergency-only */
-    val isEmergencyOnly: Flow<Boolean>
+    val isEmergencyOnly: StateFlow<Boolean>
 
     /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
-    val level: Flow<Int>
+    val level: StateFlow<Int>
 
     /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
-    val numberOfLevels: Flow<Int>
-
-    /** True when we want to draw an icon that makes room for the exclamation mark */
-    val cutOut: Flow<Boolean>
+    val numberOfLevels: StateFlow<Int>
 }
 
 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
 class MobileIconInteractorImpl(
-    defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
-    defaultMobileIconGroup: Flow<MobileIconGroup>,
+    @Application scope: CoroutineScope,
+    defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
+    defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
+    defaultMobileIconGroup: StateFlow<MobileIconGroup>,
+    override val isDefaultConnectionFailed: StateFlow<Boolean>,
     mobileMappingsProxy: MobileMappingsProxy,
     connectionRepository: MobileConnectionRepository,
 ) : MobileIconInteractor {
     private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
 
-    override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled
+    override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
+
+    override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
 
     /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
-    override val networkTypeIconGroup: Flow<MobileIconGroup> =
+    override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
         combine(
-            mobileStatusInfo,
-            defaultMobileIconMapping,
-            defaultMobileIconGroup,
-        ) { info, mapping, defaultGroup ->
-            val lookupKey =
-                when (val resolved = info.resolvedNetworkType) {
-                    is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
-                    is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
-                }
-            mapping[lookupKey] ?: defaultGroup
-        }
-
-    override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
-
-    override val level: Flow<Int> =
-        mobileStatusInfo.map { mobileModel ->
-            // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
-            if (mobileModel.isGsm) {
-                mobileModel.primaryLevel
-            } else {
-                mobileModel.cdmaLevel
+                mobileStatusInfo,
+                defaultMobileIconMapping,
+                defaultMobileIconGroup,
+            ) { info, mapping, defaultGroup ->
+                val lookupKey =
+                    when (val resolved = info.resolvedNetworkType) {
+                        is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
+                        is OverrideNetworkType ->
+                            mobileMappingsProxy.toIconKeyOverride(resolved.type)
+                    }
+                mapping[lookupKey] ?: defaultGroup
             }
-        }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+
+    override val isEmergencyOnly: StateFlow<Boolean> =
+        mobileStatusInfo
+            .mapLatest { it.isEmergencyOnly }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val level: StateFlow<Int> =
+        mobileStatusInfo
+            .mapLatest { mobileModel ->
+                // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+                if (mobileModel.isGsm) {
+                    mobileModel.primaryLevel
+                } else {
+                    mobileModel.cdmaLevel
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
     /**
      * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
      * once it's wired up inside of [CarrierConfigTracker]
      */
-    override val numberOfLevels: Flow<Int> = flowOf(4)
+    override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
 
-    /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
-    // TODO: find a better name for this?
-    override val cutOut: Flow<Boolean> = flowOf(false)
+    override val isDataConnected: StateFlow<Boolean> =
+        mobileStatusInfo
+            .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 614d583..a4175c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -19,6 +19,7 @@
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.dagger.SysUISingleton
@@ -35,7 +36,9 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -51,12 +54,16 @@
 interface MobileIconsInteractor {
     /** List of subscriptions, potentially filtered for CBRS */
     val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+    /** True if the active mobile data subscription has data enabled */
+    val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
     /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
-    val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
+    val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
     /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
-    val defaultMobileIconGroup: Flow<MobileIconGroup>
+    val defaultMobileIconGroup: StateFlow<MobileIconGroup>
+    /** True only if the default network is mobile, and validation also failed */
+    val isDefaultConnectionFailed: StateFlow<Boolean>
     /** True once the user has been set up */
-    val isUserSetup: Flow<Boolean>
+    val isUserSetup: StateFlow<Boolean>
     /**
      * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
      * subId. Will throw if the ID is invalid
@@ -79,6 +86,22 @@
     private val activeMobileDataSubscriptionId =
         mobileConnectionsRepo.activeMobileDataSubscriptionId
 
+    private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> =
+        activeMobileDataSubscriptionId
+            .mapLatest { activeId ->
+                if (activeId == INVALID_SUBSCRIPTION_ID) {
+                    null
+                } else {
+                    mobileConnectionsRepo.getRepoForSubId(activeId)
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+    override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
+        activeMobileDataConnectionRepo
+            .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
         mobileConnectionsRepo.subscriptionsFlow
 
@@ -132,22 +155,40 @@
      */
     override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
         mobileConnectionsRepo.defaultDataSubRatConfig
-            .map { mobileMappingsProxy.mapIconSets(it) }
+            .mapLatest { mobileMappingsProxy.mapIconSets(it) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
 
     /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
     override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
         mobileConnectionsRepo.defaultDataSubRatConfig
-            .map { mobileMappingsProxy.getDefaultIcons(it) }
+            .mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
 
-    override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+    /**
+     * We want to show an error state when cellular has actually failed to validate, but not if some
+     * other transport type is active, because then we expect there not to be validation.
+     */
+    override val isDefaultConnectionFailed: StateFlow<Boolean> =
+        mobileConnectionsRepo.defaultMobileNetworkConnectivity
+            .mapLatest { connectivityModel ->
+                if (!connectivityModel.isConnected) {
+                    false
+                } else {
+                    !connectivityModel.isValidated
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
 
     /** Vends out new [MobileIconInteractor] for a particular subId */
     override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
         MobileIconInteractorImpl(
+            scope,
+            activeDataConnectionHasDataEnabled,
             defaultMobileIconMapping,
             defaultMobileIconGroup,
+            isDefaultConnectionFailed,
             mobileMappingsProxy,
             mobileConnectionsRepo.getRepoForSubId(subId),
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 8131739..7869021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -24,10 +24,12 @@
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
 
 /**
  * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -39,29 +41,38 @@
  *
  * TODO: figure out where carrier merged and VCN models go (probably here?)
  */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
 class MobileIconViewModel
 constructor(
     val subscriptionId: Int,
     iconInteractor: MobileIconInteractor,
     logger: ConnectivityPipelineLogger,
 ) {
+    /** Whether or not to show the error state of [SignalDrawable] */
+    private val showExclamationMark: Flow<Boolean> =
+        iconInteractor.isDefaultDataEnabled.mapLatest { !it }
+
     /** An int consumable by [SignalDrawable] for display */
-    var iconId: Flow<Int> =
-        combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+    val iconId: Flow<Int> =
+        combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
                 level,
                 numberOfLevels,
-                cutOut ->
-                SignalDrawable.getState(level, numberOfLevels, cutOut)
+                showExclamationMark ->
+                SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
             }
             .distinctUntilChanged()
             .logOutputChange(logger, "iconId($subscriptionId)")
 
     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
-    var networkTypeIcon: Flow<Icon?> =
-        combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
-            networkTypeIconGroup,
-            isDataEnabled ->
-            if (!isDataEnabled) {
+    val networkTypeIcon: Flow<Icon?> =
+        combine(
+            iconInteractor.networkTypeIconGroup,
+            iconInteractor.isDataConnected,
+            iconInteractor.isDataEnabled,
+            iconInteractor.isDefaultConnectionFailed,
+        ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
+            if (!dataConnected || !dataEnabled || failedConnection) {
                 null
             } else {
                 val desc =
@@ -72,5 +83,5 @@
             }
         }
 
-    var tint: Flow<Int> = flowOf(Color.CYAN)
+    val tint: Flow<Int> = flowOf(Color.CYAN)
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c0..131cf7d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -72,6 +72,7 @@
     keyguardOccluded = false,
     occludingAppRequestingFp = false,
     primaryUser = false,
+    shouldListenSfpsState = false,
     shouldListenForFingerprintAssistant = false,
     switchingUser = false,
     udfps = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 52f8ef8..0a2b3d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -194,6 +194,15 @@
     }
 
     @Test
+    public void onInitConfiguresViewMode() {
+        mKeyguardSecurityContainerController.onInit();
+        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+                eq(mUserSwitcherController),
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+                eq(mFalsingA11yDelegate));
+    }
+
+    @Test
     public void showSecurityScreen_canInflateAllModes() {
         SecurityMode[] modes = SecurityMode.values();
         for (SecurityMode mode : modes) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 680c3b8..5718835 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -21,6 +21,7 @@
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
 
@@ -39,6 +40,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -54,6 +56,7 @@
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -61,18 +64,21 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.net.Uri;
 import android.nfc.NfcAdapter;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -111,6 +117,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -182,6 +189,8 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
+    private SecureSettings mSecureSettings;
+    @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
     private SensorPrivacyManager mSensorPrivacyManager;
@@ -215,6 +224,7 @@
     private GlobalSettings mGlobalSettings;
     private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
 
+
     private final int mCurrentUserId = 100;
     private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
 
@@ -224,6 +234,9 @@
     @Captor
     private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
 
+    @Mock
+    private Uri mURI;
+
     // Direct executor
     private final Executor mBackgroundExecutor = Runnable::run;
     private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
+
+        when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
+
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        ExtendedMockito.spyOn(contentResolver);
+        doNothing().when(contentResolver)
+                .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+                        anyInt());
+
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
 
         verify(mBiometricManager)
@@ -1136,6 +1158,63 @@
     }
 
     @Test
+    public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+            throws RemoteException {
+        // SFPS supported and enrolled
+        final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+        props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+        when(mAuthController.getSfpsProps()).thenReturn(props);
+        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+        // WHEN require screen on to auth is disabled, and keyguard is not awake
+        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
+        mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+
+        // Preconditions for sfps auth to run
+        keyguardNotGoingAway();
+        currentUserIsPrimary();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        biometricsEnabledForCurrentUser();
+        userNotCurrentlySwitching();
+
+        statusBarShadeIsLocked();
+        mTestableLooper.processAllMessages();
+
+        // THEN we should listen for sfps when screen off, because require screen on is disabled
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+        // WHEN require screen on to auth is enabled, and keyguard is not awake
+        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
+        mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+        // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+
+        // Device now awake & keyguard is now interactive
+        deviceNotGoingToSleep();
+        deviceIsInteractive();
+        keyguardIsVisible();
+
+        // THEN we should listen for sfps when screen on, and require screen on is enabled
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+    }
+
+    private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
+            @FingerprintSensorProperties.SensorType int sensorType) {
+        return new FingerprintSensorPropertiesInternal(
+                0 /* sensorId */,
+                SensorProperties.STRENGTH_STRONG,
+                1 /* maxEnrollmentsPerUser */,
+                new ArrayList<ComponentInfoInternal>(),
+                sensorType,
+                true /* resetLockoutRequiresHardwareAuthToken */);
+    }
+
+    @Test
     public void testShouldNotListenForUdfps_whenTrustEnabled() {
         // GIVEN a "we should listen for udfps" state
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
@@ -1804,7 +1883,7 @@
         protected TestableKeyguardUpdateMonitor(Context context) {
             super(context,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
-                    mBroadcastDispatcher, mDumpManager,
+                    mBroadcastDispatcher, mSecureSettings, mDumpManager,
                     mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index d489656..6ec5a6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -42,6 +42,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -103,6 +105,8 @@
     @Mock private lateinit var udfpsView: UdfpsView
     @Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
     @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var bouncerInteractor: BouncerInteractor
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -136,7 +140,8 @@
             keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
             configurationController, systemClock, keyguardStateController,
             unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
-            controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
+            controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
+            bouncerInteractor, isDebuggable
         )
         block()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index ed957db..41c307a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -71,6 +71,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -192,6 +193,8 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock
     private AlternateUdfpsTouchProvider mAlternateTouchProvider;
+    @Mock
+    private BouncerInteractor mBouncerInteractor;
 
     // Capture listeners so that they can be used to send events
     @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
@@ -272,7 +275,8 @@
                 mLatencyTracker,
                 mActivityLaunchAnimator,
                 Optional.of(mAlternateTouchProvider),
-                mBiometricsExecutor);
+                mBiometricsExecutor,
+                mBouncerInteractor);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
index 7864f21b..1bc237d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
@@ -23,7 +23,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -48,7 +48,7 @@
     @Mock
     private AuthController mAuthController;
     @Mock
-    private IUdfpsHbmListener mDisplayCallback;
+    private IUdfpsRefreshRateRequestCallback mDisplayCallback;
     @Mock
     private UdfpsLogger mUdfpsLogger;
     @Mock
@@ -68,7 +68,7 @@
         when(contextSpy.getDisplayId()).thenReturn(DISPLAY_ID);
 
         // Set up mocks.
-        when(mAuthController.getUdfpsHbmListener()).thenReturn(mDisplayCallback);
+        when(mAuthController.getUdfpsRefreshRateCallback()).thenReturn(mDisplayCallback);
 
         // Create a real controller with mock dependencies.
         mHbmController = new UdfpsDisplayMode(contextSpy, mExecution, mAuthController,
@@ -81,7 +81,7 @@
         mHbmController.enable(mOnEnabled);
 
         // Should set the appropriate refresh rate for UDFPS and notify the caller.
-        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
         verify(mOnEnabled).run();
 
         // Disable the UDFPS mode.
@@ -89,7 +89,7 @@
 
         // Should unset the refresh rate and notify the caller.
         verify(mOnDisabled).run();
-        verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID));
     }
 
     @Test
@@ -98,7 +98,7 @@
         mHbmController.enable(mOnEnabled);
 
         // Should set the appropriate refresh rate for UDFPS and notify the caller.
-        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
         verify(mOnEnabled).run();
 
         // Second request to enable the UDFPS mode, while it's still enabled.
@@ -115,7 +115,7 @@
         mHbmController.enable(mOnEnabled);
 
         // Should set the appropriate refresh rate for UDFPS and notify the caller.
-        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
         verify(mOnEnabled).run();
 
         // First request to disable the UDFPS mode.
@@ -123,7 +123,7 @@
 
         // Should unset the refresh rate and notify the caller.
         verify(mOnDisabled).run();
-        verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID));
 
         // Second request to disable the UDFPS mode, when it's already disabled.
         mHbmController.disable(mOnDisabled);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
new file mode 100644
index 0000000..e5c7a42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
+    // Dependencies
+    protected @Mock UdfpsKeyguardView mView;
+    protected @Mock Context mResourceContext;
+    protected @Mock StatusBarStateController mStatusBarStateController;
+    protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
+    protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    protected @Mock DumpManager mDumpManager;
+    protected @Mock DelayableExecutor mExecutor;
+    protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    protected @Mock KeyguardStateController mKeyguardStateController;
+    protected @Mock KeyguardViewMediator mKeyguardViewMediator;
+    protected @Mock ConfigurationController mConfigurationController;
+    protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    protected @Mock SystemUIDialogManager mDialogManager;
+    protected @Mock UdfpsController mUdfpsController;
+    protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+    protected @Mock KeyguardBouncer mBouncer;
+    protected @Mock BouncerInteractor mBouncerInteractor;
+
+    protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+    protected FakeSystemClock mSystemClock = new FakeSystemClock();
+
+    protected UdfpsKeyguardViewController mController;
+
+    // Capture listeners so that they can be used to send events
+    private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+    protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+    private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
+    protected List<ShadeExpansionListener> mExpansionListeners;
+
+    private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
+            mAltAuthInterceptorCaptor;
+    protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+
+    private @Captor ArgumentCaptor<KeyguardStateController.Callback>
+            mKeyguardStateControllerCallbackCaptor;
+    protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mView.getContext()).thenReturn(mResourceContext);
+        when(mResourceContext.getString(anyInt())).thenReturn("test string");
+        when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
+        when(mView.getUnpausedAlpha()).thenReturn(255);
+        mController = createUdfpsKeyguardViewController();
+    }
+
+    protected void sendStatusBarStateChanged(int statusBarState) {
+        mStatusBarStateListener.onStateChanged(statusBarState);
+    }
+
+    protected void captureStatusBarStateListeners() {
+        verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+        mStatusBarStateListener = mStateListenerCaptor.getValue();
+    }
+
+    protected void captureStatusBarExpansionListeners() {
+        verify(mShadeExpansionStateManager, times(2))
+                .addExpansionListener(mExpansionListenerCaptor.capture());
+        // first (index=0) is from super class, UdfpsAnimationViewController.
+        // second (index=1) is from UdfpsKeyguardViewController
+        mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+    }
+
+    protected void updateStatusBarExpansion(float fraction, boolean expanded) {
+        ShadeExpansionChangeEvent event =
+                new ShadeExpansionChangeEvent(
+                        fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
+        for (ShadeExpansionListener listener : mExpansionListeners) {
+            listener.onPanelExpansionChanged(event);
+        }
+    }
+
+    protected void captureAltAuthInterceptor() {
+        verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
+                mAltAuthInterceptorCaptor.capture());
+        mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+    }
+
+    protected void captureKeyguardStateControllerCallback() {
+        verify(mKeyguardStateController).addCallback(
+                mKeyguardStateControllerCallbackCaptor.capture());
+        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+    }
+
+    public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+        return createUdfpsKeyguardViewController(false);
+    }
+
+    protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
+            boolean useModernBouncer) {
+        mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+        when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(
+                useModernBouncer ? null : mBouncer);
+        return new UdfpsKeyguardViewController(
+                mView,
+                mStatusBarStateController,
+                mShadeExpansionStateManager,
+                mStatusBarKeyguardViewManager,
+                mKeyguardUpdateMonitor,
+                mDumpManager,
+                mLockscreenShadeTransitionController,
+                mConfigurationController,
+                mSystemClock,
+                mKeyguardStateController,
+                mUnlockedScreenOffAnimationController,
+                mDialogManager,
+                mUdfpsController,
+                mActivityLaunchAnimator,
+                mFeatureFlags,
+                mBouncerInteractor);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index c0f9c82..55b6194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -25,126 +25,53 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
-public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
-    // Dependencies
-    @Mock
-    private UdfpsKeyguardView mView;
-    @Mock
-    private Context mResourceContext;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private ShadeExpansionStateManager mShadeExpansionStateManager;
-    @Mock
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock
-    private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock
-    private DumpManager mDumpManager;
-    @Mock
-    private DelayableExecutor mExecutor;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardViewMediator mKeyguardViewMediator;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-    @Mock
-    private SystemUIDialogManager mDialogManager;
-    @Mock
-    private UdfpsController mUdfpsController;
-    @Mock
-    private ActivityLaunchAnimator mActivityLaunchAnimator;
-    private FakeSystemClock mSystemClock = new FakeSystemClock();
+public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
+    private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback>
+            mBouncerExpansionCallbackCaptor;
+    private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
 
-    private UdfpsKeyguardViewController mController;
-
-    // Capture listeners so that they can be used to send events
-    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
-    private StatusBarStateController.StateListener mStatusBarStateListener;
-
-    @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
-    private List<ShadeExpansionListener> mExpansionListeners;
-
-    @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
-            mAltAuthInterceptorCaptor;
-    private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
-
-    @Captor private ArgumentCaptor<KeyguardStateController.Callback>
-            mKeyguardStateControllerCallbackCaptor;
-    private KeyguardStateController.Callback mKeyguardStateControllerCallback;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mView.getContext()).thenReturn(mResourceContext);
-        when(mResourceContext.getString(anyInt())).thenReturn("test string");
-        when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
-        when(mView.getUnpausedAlpha()).thenReturn(255);
-        mController = new UdfpsKeyguardViewController(
-                mView,
-                mStatusBarStateController,
-                mShadeExpansionStateManager,
-                mStatusBarKeyguardViewManager,
-                mKeyguardUpdateMonitor,
-                mDumpManager,
-                mLockscreenShadeTransitionController,
-                mConfigurationController,
-                mSystemClock,
-                mKeyguardStateController,
-                mUnlockedScreenOffAnimationController,
-                mDialogManager,
-                mUdfpsController,
-                mActivityLaunchAnimator);
+    @Override
+    public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
     }
 
     @Test
+    public void testShouldPauseAuth_bouncerShowing() {
+        mController.onViewAttached();
+        captureStatusBarStateListeners();
+        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+        captureBouncerExpansionCallback();
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+
+        assertTrue(mController.shouldPauseAuth());
+    }
+
+
+
+    @Test
     public void testRegistersExpansionChangedListenerOnAttached() {
         mController.onViewAttached();
         captureStatusBarExpansionListeners();
@@ -202,20 +129,6 @@
     }
 
     @Test
-    public void testShouldPauseAuthBouncerShowing() {
-        mController.onViewAttached();
-        captureStatusBarStateListeners();
-        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
-        captureAltAuthInterceptor();
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
-        mAltAuthInterceptor.onBouncerVisibilityChanged();
-
-        assertTrue(mController.shouldPauseAuth());
-    }
-
-    @Test
     public void testShouldPauseAuthUnpausedAlpha0() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
@@ -503,41 +416,8 @@
         verify(mView, atLeastOnce()).setPauseAuth(false);
     }
 
-    private void sendStatusBarStateChanged(int statusBarState) {
-        mStatusBarStateListener.onStateChanged(statusBarState);
-    }
-
-    private void captureStatusBarStateListeners() {
-        verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
-        mStatusBarStateListener = mStateListenerCaptor.getValue();
-    }
-
-    private void captureStatusBarExpansionListeners() {
-        verify(mShadeExpansionStateManager, times(2))
-                .addExpansionListener(mExpansionListenerCaptor.capture());
-        // first (index=0) is from super class, UdfpsAnimationViewController.
-        // second (index=1) is from UdfpsKeyguardViewController
-        mExpansionListeners = mExpansionListenerCaptor.getAllValues();
-    }
-
-    private void updateStatusBarExpansion(float fraction, boolean expanded) {
-        ShadeExpansionChangeEvent event =
-                new ShadeExpansionChangeEvent(
-                        fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
-        for (ShadeExpansionListener listener : mExpansionListeners) {
-            listener.onPanelExpansionChanged(event);
-        }
-    }
-
-    private void captureAltAuthInterceptor() {
-        verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
-                mAltAuthInterceptorCaptor.capture());
-        mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
-    }
-
-    private void captureKeyguardStateControllerCallback() {
-        verify(mKeyguardStateController).addCallback(
-                mKeyguardStateControllerCallbackCaptor.capture());
-        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+    private void captureBouncerExpansionCallback() {
+        verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
+        mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..7b19768
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
+    lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+
+    @Before
+    override fun setUp() {
+        allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
+        MockitoAnnotations.initMocks(this)
+        keyguardBouncerRepository =
+            KeyguardBouncerRepository(
+                mock(com.android.keyguard.ViewMediatorCallback::class.java),
+                mKeyguardUpdateMonitor
+            )
+        super.setUp()
+    }
+
+    override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? {
+        mBouncerInteractor =
+            BouncerInteractor(
+                keyguardBouncerRepository,
+                mock(BouncerView::class.java),
+                mock(Handler::class.java),
+                mKeyguardStateController,
+                mock(KeyguardSecurityModel::class.java),
+                mock(BouncerCallbackInteractor::class.java),
+                mock(FalsingCollector::class.java),
+                mock(DismissCallbackRegistry::class.java),
+                mock(KeyguardBypassController::class.java),
+                mKeyguardUpdateMonitor
+            )
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+    }
+
+    /** After migration, replaces LockIconViewControllerTest version */
+    @Test
+    fun testShouldPauseAuthBouncerShowing() =
+        runBlocking(IMMEDIATE) {
+            // GIVEN view attached and we're on the keyguard
+            mController.onViewAttached()
+            captureStatusBarStateListeners()
+            sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+
+            // WHEN the bouncer expansion is VISIBLE
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setVisible(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+            yield()
+
+            // THEN UDFPS shouldPauseAuth == true
+            assertTrue(mController.shouldPauseAuth())
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
new file mode 100644
index 0000000..54f20db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.any
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+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.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenEver
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class UdfpsShellTest : SysuiTestCase() {
+
+    @JvmField @Rule var rule = MockitoJUnit.rule()
+
+    // Unit under test
+    private lateinit var udfpsShell: UdfpsShell
+
+    @Mock lateinit var commandRegistry: CommandRegistry
+    @Mock lateinit var udfpsOverlay: UdfpsOverlay
+    @Mock lateinit var udfpsOverlayController: UdfpsOverlayController
+
+    @Captor private lateinit var motionEvent: ArgumentCaptor<MotionEvent>
+
+    private val sensorBounds = Rect()
+
+    @Before
+    fun setup() {
+        whenEver(udfpsOverlayController.sensorBounds).thenReturn(sensorBounds)
+
+        udfpsShell = UdfpsShell(commandRegistry, udfpsOverlay)
+        udfpsShell.udfpsOverlayController = udfpsOverlayController
+    }
+
+    @Test
+    fun testSimFingerDown() {
+        udfpsShell.simFingerDown()
+
+        verify(udfpsOverlayController, times(2)).debugOnTouch(any(), motionEvent.capture())
+
+        assertEquals(motionEvent.allValues[0].action, MotionEvent.ACTION_DOWN) // ACTION_MOVE
+        assertEquals(motionEvent.allValues[1].action, MotionEvent.ACTION_MOVE) // ACTION_MOVE
+    }
+
+    @Test
+    fun testSimFingerUp() {
+        udfpsShell.simFingerUp()
+
+        verify(udfpsOverlayController).debugOnTouch(any(), motionEvent.capture())
+
+        assertEquals(motionEvent.value.action, MotionEvent.ACTION_UP)
+    }
+
+    @Test
+    fun testOnUiReady() {
+        udfpsShell.onUiReady()
+
+        verify(udfpsOverlayController).debugOnUiReady(any(), any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9ab9d3..53d9b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -21,8 +21,10 @@
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.doze.DozeHost
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
@@ -46,6 +48,7 @@
     @Mock private lateinit var dozeHost: DozeHost
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -59,6 +62,7 @@
                 keyguardStateController,
                 dozeHost,
                 wakefulnessLifecycle,
+                biometricUnlockController,
             )
     }
 
@@ -190,7 +194,7 @@
     }
 
     @Test
-    fun wakefullness() = runBlockingTest {
+    fun wakefulness() = runBlockingTest {
         val values = mutableListOf<WakefulnessModel>()
         val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
 
@@ -217,4 +221,63 @@
         job.cancel()
         verify(wakefulnessLifecycle).removeObserver(captor.value)
     }
+
+    @Test
+    fun isBouncerShowing() = runBlockingTest {
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+        var latest: Boolean? = null
+        val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        val captor = argumentCaptor<KeyguardStateController.Callback>()
+        verify(keyguardStateController).addCallback(captor.capture())
+
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+        captor.value.onBouncerShowingChanged()
+        assertThat(latest).isTrue()
+
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+        captor.value.onBouncerShowingChanged()
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun biometricUnlockState() = runBlockingTest {
+        val values = mutableListOf<BiometricUnlockModel>()
+        val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+        val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+        verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+        captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+        assertThat(values)
+            .isEqualTo(
+                listOf(
+                    // Initial value will be NONE, followed by onModeChanged() call
+                    BiometricUnlockModel.NONE,
+                    BiometricUnlockModel.NONE,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+                    BiometricUnlockModel.SHOW_BOUNCER,
+                    BiometricUnlockModel.ONLY_WAKE,
+                    BiometricUnlockModel.UNLOCK_COLLAPSING,
+                    BiometricUnlockModel.DISMISS_BOUNCER,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+                )
+            )
+
+        job.cancel()
+        verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 64913c7c..27d5d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -128,11 +128,15 @@
 
             assertThat(steps.size).isEqualTo(3)
             assertThat(steps[0])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
             assertThat(steps[1])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME)
+                )
             assertThat(steps[2])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)
+                )
             job.cancel()
         }
 
@@ -174,15 +178,22 @@
     }
 
     private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
-        assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+        assertThat(steps[0])
+            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
         fractions.forEachIndexed { index, fraction ->
             assertThat(steps[index + 1])
                 .isEqualTo(
-                    TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+                    TransitionStep(
+                        AOD,
+                        LOCKSCREEN,
+                        fraction.toFloat(),
+                        TransitionState.RUNNING,
+                        OWNER_NAME
+                    )
                 )
         }
         assertThat(steps[steps.size - 1])
-            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
 
         assertThat(wtfHandler.failed).isFalse()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
index e6c8dd8..5743b2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -27,8 +27,8 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.plugins.ActivityStarter
@@ -57,6 +57,7 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var repository: KeyguardBouncerRepository
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
     @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
@@ -86,6 +87,7 @@
             )
         `when`(repository.startingDisappearAnimation.value).thenReturn(null)
         `when`(repository.show.value).thenReturn(null)
+        `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
     }
 
     @Test
@@ -97,7 +99,7 @@
         verify(repository).setHide(false)
         verify(repository).setStartingToHide(false)
         verify(repository).setScrimmed(true)
-        verify(repository).setExpansion(EXPANSION_VISIBLE)
+        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
         verify(repository).setShowingSoon(true)
         verify(keyguardStateController).notifyBouncerShowing(true)
         verify(bouncerCallbackInteractor).dispatchStartingToShow()
@@ -108,7 +110,7 @@
 
     @Test
     fun testShow_isNotScrimmed() {
-        verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
+        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
     }
 
     @Test
@@ -124,7 +126,6 @@
         verify(falsingCollector).onBouncerHidden()
         verify(keyguardStateController).notifyBouncerShowing(false)
         verify(repository).setShowingSoon(false)
-        verify(repository).setOnDismissAction(null)
         verify(repository).setVisible(false)
         verify(repository).setHide(true)
         verify(repository).setShow(null)
@@ -132,26 +133,26 @@
 
     @Test
     fun testExpansion() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
-        bouncerInteractor.setExpansion(0.6f)
-        verify(repository).setExpansion(0.6f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        bouncerInteractor.setPanelExpansion(0.6f)
+        verify(repository).setPanelExpansion(0.6f)
         verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
     }
 
     @Test
     fun testExpansion_fullyShown() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
+        bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
         verify(falsingCollector).onBouncerShown()
         verify(bouncerCallbackInteractor).dispatchFullyShown()
     }
 
     @Test
     fun testExpansion_fullyHidden() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
+        bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
         verify(repository).setVisible(false)
         verify(repository).setShow(null)
         verify(falsingCollector).onBouncerHidden()
@@ -161,8 +162,8 @@
 
     @Test
     fun testExpansion_startingToHide() {
-        `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        bouncerInteractor.setExpansion(0.1f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        bouncerInteractor.setPanelExpansion(0.1f)
         verify(repository).setStartingToHide(true)
         verify(bouncerCallbackInteractor).dispatchStartingToHide()
     }
@@ -178,8 +179,7 @@
         val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
         val cancelAction = mock(Runnable::class.java)
         bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
-        verify(repository)
-            .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
     }
 
     @Test
@@ -234,7 +234,7 @@
     @Test
     fun testIsFullShowing() {
         `when`(repository.isVisible.value).thenReturn(true)
-        `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
         `when`(repository.startingDisappearAnimation.value).thenReturn(null)
         assertThat(bouncerInteractor.isFullyShowing()).isTrue()
         `when`(repository.isVisible.value).thenReturn(false)
@@ -255,7 +255,7 @@
         assertThat(bouncerInteractor.isInTransit()).isTrue()
         `when`(repository.showingSoon.value).thenReturn(false)
         assertThat(bouncerInteractor.isInTransit()).isFalse()
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         assertThat(bouncerInteractor.isInTransit()).isTrue()
     }
 
@@ -269,10 +269,9 @@
 
     @Test
     fun testWillDismissWithAction() {
-        `when`(repository.onDismissAction.value?.onDismissAction)
-            .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+        `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
         assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
-        `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+        `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
         assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 0424c28..6333b24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -54,9 +54,11 @@
     @Test
     fun `transition collectors receives only appropriate events`() =
         runBlocking(IMMEDIATE) {
-            var goneToAodSteps = mutableListOf<TransitionStep>()
+            var lockscreenToAodSteps = mutableListOf<TransitionStep>()
             val job1 =
-                underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this)
+                underTest.lockscreenToAodTransition
+                    .onEach { lockscreenToAodSteps.add(it) }
+                    .launchIn(this)
 
             var aodToLockscreenSteps = mutableListOf<TransitionStep>()
             val job2 =
@@ -70,14 +72,14 @@
             steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
             steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
             steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
-            steps.add(TransitionStep(GONE, AOD, 0f, STARTED))
-            steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING))
-            steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
 
             steps.forEach { repository.sendTransitionStep(it) }
 
             assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
-            assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8))
+            assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
 
             job1.cancel()
             job2.cancel()
@@ -119,10 +121,8 @@
     fun keyguardStateTests() =
         runBlocking(IMMEDIATE) {
             var finishedSteps = mutableListOf<KeyguardState>()
-            val job1 =
+            val job =
                 underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
-            var startedSteps = mutableListOf<KeyguardState>()
-            val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this)
 
             val steps = mutableListOf<TransitionStep>()
 
@@ -137,10 +137,60 @@
             steps.forEach { repository.sendTransitionStep(it) }
 
             assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
-            assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE))
 
-            job1.cancel()
-            job2.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun finishedKeyguardTransitionStepTests() =
+        runBlocking(IMMEDIATE) {
+            var finishedSteps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.finishedKeyguardTransitionStep
+                    .onEach { finishedSteps.add(it) }
+                    .launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
+
+            job.cancel()
+        }
+
+    @Test
+    fun startedKeyguardTransitionStepTests() =
+        runBlocking(IMMEDIATE) {
+            var startedSteps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.startedKeyguardTransitionStep
+                    .onEach { startedSteps.add(it) }
+                    .launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+
+            job.cancel()
         }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 6adce7a..c1fa9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -61,6 +61,7 @@
 import android.view.DisplayInfo;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -201,6 +202,8 @@
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
     private Resources mResources;
+    @Mock
+    private ViewRootImpl mViewRootImpl;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
 
@@ -227,6 +230,7 @@
         when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
                 .thenReturn(mContext);
         when(mNavigationBarView.getResources()).thenReturn(mResources);
+        when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl);
         setupSysuiDependency();
         // This class inflates views that call Dependency.get, thus these injections are still
         // necessary.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
index 99a17a6..9115ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
@@ -24,6 +24,7 @@
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
@@ -48,6 +49,7 @@
     public void setUp() throws Exception {
         mTestableLooper = TestableLooper.get(this);
         LayoutInflater inflater = LayoutInflater.from(mContext);
+        mContext.ensureTestableResources();
         mTestableLooper.runWithLooper(() ->
                 mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null));
 
@@ -119,4 +121,30 @@
         mQSCarrier.updateState(c, true);
         assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
     }
+
+    @Test
+    public void testCarrierNameMaxWidth_smallScreen_fromResource() {
+        int maxEms = 10;
+        mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_use_large_screen_shade_header, false);
+        TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+        mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+        assertEquals(maxEms, carrierText.getMaxEms());
+    }
+
+    @Test
+    public void testCarrierNameMaxWidth_largeScreen_maxInt() {
+        int maxEms = 10;
+        mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_use_large_screen_shade_header, true);
+        TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+        mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+        assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 2c76be6..b067ee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
@@ -136,6 +137,20 @@
         assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
     }
 
+    @Test
+    public void testIconNotAnimatedWhenAllowAnimationsFalse() {
+        ImageView iv = new ImageView(mContext);
+        AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class);
+        State s = new State();
+        s.icon = mock(Icon.class);
+        when(s.icon.getDrawable(any())).thenReturn(d);
+        when(s.icon.getInvisibleDrawable(any())).thenReturn(d);
+
+        mIconView.updateIcon(iv, s, false);
+
+        verify(d, never()).start();
+    }
+
     private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
         return new Drawable.ConstantState() {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 45b4353..c98c1f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -486,6 +486,7 @@
                 mSysUiState,
                 () -> mKeyguardBottomAreaViewController,
                 mKeyguardUnlockAnimationController,
+                mKeyguardIndicationController,
                 mNotificationListContainer,
                 mNotificationStackSizeCalculator,
                 mUnlockedScreenOffAnimationController,
@@ -499,8 +500,6 @@
                 () -> {},
                 mNotificationShelfController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
-        mNotificationPanelViewController.setKeyguardIndicationController(
-                mKeyguardIndicationController);
         ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
         verify(mView, atLeast(1)).addOnAttachStateChangeListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 26a0770..a4a7995 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -152,7 +152,7 @@
 
         // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we should intercept touch
@@ -165,7 +165,7 @@
 
         // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we shouldn't intercept touch
@@ -178,7 +178,7 @@
 
         // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 09add65..43c6942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -66,6 +66,8 @@
     private lateinit var dumpManager: DumpManager
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
+    @Mock
+    private lateinit var shadeLogger: ShadeLogger
 
     private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
     private lateinit var underTest: PulsingGestureListener
@@ -81,6 +83,7 @@
                 centralSurfaces,
                 ambientDisplayConfiguration,
                 statusBarStateController,
+                shadeLogger,
                 tunerService,
                 dumpManager
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index cf7f8dd..08933fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -33,9 +33,10 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -135,27 +136,29 @@
     public void testOnSystemBarAttributesChanged() {
         doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+                BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+                TEST_LETTERBOX_DETAILS);
     }
 
     @Test
     public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
         doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+                BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+                TEST_LETTERBOX_DETAILS);
     }
 
     private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                 letterboxDetails);
         waitForIdleSync();
         verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
                 eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
-                eq(requestedVisibilities), eq(packageName), eq(letterboxDetails));
+                eq(requestedVisibleTypes), eq(packageName), eq(letterboxDetails));
     }
 
     @Test
@@ -492,11 +495,12 @@
     }
 
     @Test
-    public void testSetUdfpsHbmListener() {
-        final IUdfpsHbmListener listener = mock(IUdfpsHbmListener.class);
-        mCommandQueue.setUdfpsHbmListener(listener);
+    public void testSetUdfpsRefreshRateCallback() {
+        final IUdfpsRefreshRateRequestCallback callback =
+                mock(IUdfpsRefreshRateRequestCallback.class);
+        mCommandQueue.setUdfpsRefreshRateCallback(callback);
         waitForIdleSync();
-        verify(mCallbacks).setUdfpsHbmListener(eq(listener));
+        verify(mCallbacks).setUdfpsRefreshRateCallback(eq(callback));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e7a435e..112e759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -22,6 +22,7 @@
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -60,6 +61,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -192,6 +194,25 @@
     }
 
     /**
+     * Creates a generic row with rounded border.
+     *
+     * @return a generic row with the set roundness.
+     * @throws Exception
+     */
+    public ExpandableNotificationRow createRowWithRoundness(
+            float topRoundness,
+            float bottomRoundness,
+            SourceType sourceType
+    ) throws Exception {
+        ExpandableNotificationRow row = createRow();
+        row.requestTopRoundness(topRoundness, false, sourceType);
+        row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+        assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
+        assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
+        return row;
+    }
+
+    /**
      * Creates a generic row.
      *
      * @return a generic row with no special properties.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 7c41abba..438b528 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,6 +25,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
@@ -151,4 +152,37 @@
         Assert.assertNotNull("Children container must have a header after recreation",
                 mChildrenContainer.getCurrentHeaderView());
     }
+
+    @Test
+    public void addNotification_shouldResetOnScrollRoundness() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.OnScroll);
+
+        mChildrenContainer.addNotification(row, 0);
+
+        Assert.assertEquals(0f, row.getTopRoundness(), /* delta = */ 0f);
+        Assert.assertEquals(0f, row.getBottomRoundness(), /* delta = */ 0f);
+    }
+
+    @Test
+    public void addNotification_shouldNotResetOtherRoundness() throws Exception {
+        ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.DefaultValue);
+        ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.OnDismissAnimation);
+
+        mChildrenContainer.addNotification(row1, 0);
+        mChildrenContainer.addNotification(row2, 0);
+
+        Assert.assertEquals(1f, row1.getTopRoundness(), /* delta = */ 0f);
+        Assert.assertEquals(1f, row1.getBottomRoundness(), /* delta = */ 0f);
+        Assert.assertEquals(1f, row2.getTopRoundness(), /* delta = */ 0f);
+        Assert.assertEquals(1f, row2.getBottomRoundness(), /* delta = */ 0f);
+    }
 }
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
index 7741813..bda2336 100644
--- 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
@@ -1,6 +1,7 @@
 package com.android.systemui.statusbar.notification.stack
 
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
@@ -8,8 +9,10 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.SourceType
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
 import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
@@ -37,6 +40,13 @@
     private val shelfState = shelf.viewState as NotificationShelf.ShelfState
     private val ambientState = mock(AmbientState::class.java)
     private val hostLayoutController: NotificationStackScrollLayoutController = mock()
+    private val notificationTestHelper by lazy {
+        allowTestableLooperAsMainThread()
+        NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this))
+    }
 
     @Before
     fun setUp() {
@@ -299,6 +309,39 @@
         )
     }
 
+    @Test
+    fun resetOnScrollRoundness_shouldSetOnScrollTo0() {
+        val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.OnScroll)
+
+        NotificationShelf.resetOnScrollRoundness(row)
+
+        assertEquals(0f, row.topRoundness)
+        assertEquals(0f, row.bottomRoundness)
+    }
+
+    @Test
+    fun resetOnScrollRoundness_shouldNotResetOtherRoundness() {
+        val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.DefaultValue)
+        val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.OnDismissAnimation)
+
+        NotificationShelf.resetOnScrollRoundness(row1)
+        NotificationShelf.resetOnScrollRoundness(row2)
+
+        assertEquals(1f, row1.topRoundness)
+        assertEquals(1f, row1.bottomRoundness)
+        assertEquals(1f, row2.topRoundness)
+        assertEquals(1f, row2.bottomRoundness)
+    }
+
     private fun setFractionToShade(fraction: Float) {
         whenever(ambientState.fractionToShade).thenReturn(fraction)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 4dea6be..b850cf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -136,7 +136,7 @@
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
-        mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
+        mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index e1f20d5..5ebaf69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -29,7 +29,7 @@
 import android.os.PowerManager;
 import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
 
 import androidx.test.filters.SmallTest;
 
@@ -177,7 +177,7 @@
         AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
         boolean navbarColorManagedByIme = true;
         int behavior = 456;
-        InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        int requestedVisibleTypes = WindowInsets.Type.systemBars();
         String packageName = "test package name";
         LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
 
@@ -187,7 +187,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails);
 
@@ -197,7 +197,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails
         );
@@ -209,7 +209,7 @@
         AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
         boolean navbarColorManagedByIme = true;
         int behavior = 456;
-        InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        int requestedVisibleTypes = WindowInsets.Type.systemBars();
         String packageName = "test package name";
         LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
 
@@ -219,7 +219,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5755782..7ce3a67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -500,7 +500,7 @@
                 mKeyguardVieMediatorCallback);
 
         // TODO: we should be able to call mCentralSurfaces.start() and have all the below values
-        // initialized automatically.
+        // initialized automatically and make NPVC private.
         mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
         mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController;
         mCentralSurfaces.mDozeScrimController = mDozeScrimController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index fca9771..287ebba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -30,6 +30,7 @@
 import android.view.Display;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 import androidx.lifecycle.Observer;
@@ -107,7 +108,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
         assertTrue(mLightsOutNotifController.areLightsOut());
@@ -121,7 +122,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
         assertFalse(mLightsOutNotifController.areLightsOut());
@@ -153,7 +154,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
@@ -174,7 +175,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
@@ -195,7 +196,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0c35659..7166666 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
@@ -307,7 +307,7 @@
 
     @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
-        when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+        when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(
                 expansionEvent(
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
@@ -361,7 +361,7 @@
 
     @Test
     public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
-        when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+        when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
         mStatusBarKeyguardViewManager.show(null);
 
         mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
index 9957c2a..4d79a53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -3,12 +3,9 @@
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.view.Display
-import android.view.InsetsVisibilities
+import android.view.WindowInsets
 import android.view.WindowInsetsController
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
-import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
-import android.view.WindowInsetsController.Appearance
+import android.view.WindowInsetsController.*
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
@@ -27,8 +24,8 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -88,7 +85,7 @@
         changeSysBarAttrs(TEST_APPEARANCE)
 
         verify(statusBarStateController)
-            .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), any(), any())
+            .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -97,7 +94,7 @@
 
         verify(statusBarStateController)
             .setSystemBarAttributes(
-                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -130,7 +127,7 @@
 
         verify(statusBarStateController)
             .setSystemBarAttributes(
-                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -197,7 +194,7 @@
             appearanceRegions,
             /* navbarColorManagedByIme= */ false,
             WindowInsetsController.BEHAVIOR_DEFAULT,
-            InsetsVisibilities(),
+            WindowInsets.Type.defaultVisible(),
             "package name",
             letterboxDetails)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index de1fec8..288f54c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -17,16 +17,18 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeMobileConnectionRepository : MobileConnectionRepository {
     private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
-    override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+    override val subscriptionModelFlow = _subscriptionsModelFlow
 
     private val _dataEnabled = MutableStateFlow(true)
     override val dataEnabled = _dataEnabled
 
+    private val _isDefaultDataSubscription = MutableStateFlow(true)
+    override val isDefaultDataSubscription = _isDefaultDataSubscription
+
     fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
         _subscriptionsModelFlow.value = model
     }
@@ -34,4 +36,8 @@
     fun setDataEnabled(enabled: Boolean) {
         _dataEnabled.value = enabled
     }
+
+    fun setIsDefaultDataSubscription(isDefault: Boolean) {
+        _isDefaultDataSubscription.value = isDefault
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 813e750..533d5d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -17,8 +17,9 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -26,18 +27,26 @@
     private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
     override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
 
-    private val _activeMobileDataSubscriptionId =
-        MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+    private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
     override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
 
     private val _defaultDataSubRatConfig = MutableStateFlow(Config())
     override val defaultDataSubRatConfig = _defaultDataSubRatConfig
 
+    private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+    override val defaultDataSubId = _defaultDataSubId
+
+    private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
+    override val defaultMobileNetworkConnectivity = _mobileConnectivity
+
     private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
         return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
     }
 
+    private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
+    override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
+
     fun setSubscriptions(subs: List<SubscriptionInfo>) {
         _subscriptionsFlow.value = subs
     }
@@ -46,6 +55,18 @@
         _defaultDataSubRatConfig.value = config
     }
 
+    fun setDefaultDataSubId(id: Int) {
+        _defaultDataSubId.value = id
+    }
+
+    fun setMobileConnectivity(model: MobileConnectivityModel) {
+        _mobileConnectivity.value = model
+    }
+
+    suspend fun triggerGlobalMobileDataSettingChangedEvent() {
+        _globalMobileDataSettingChangedEvent.emit(Unit)
+    }
+
     fun setActiveMobileDataSubscriptionId(subId: Int) {
         _activeMobileDataSubscriptionId.value = subId
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
index 6c495c5..141b50c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -16,13 +16,12 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /** Defaults to `true` */
 class FakeUserSetupRepository : UserSetupRepository {
     private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
-    override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+    override val isUserSetupFlow = _isUserSetup
 
     fun setUserSetup(setup: Boolean) {
         _isUserSetup.value = setup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 0939364..5ce51bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.os.UserHandle
+import android.provider.Settings
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
 import android.telephony.SignalStrength
@@ -42,6 +44,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -67,16 +70,23 @@
     @Mock private lateinit var logger: ConnectivityPipelineLogger
 
     private val scope = CoroutineScope(IMMEDIATE)
+    private val globalSettings = FakeSettings()
+    private val connectionsRepo = FakeMobileConnectionsRepository()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        globalSettings.userId = UserHandle.USER_ALL
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
 
         underTest =
             MobileConnectionRepositoryImpl(
+                context,
                 SUB_1_ID,
                 telephonyManager,
+                globalSettings,
+                connectionsRepo.defaultDataSubId,
+                connectionsRepo.globalMobileDataSettingChangedEvent,
                 IMMEDIATE,
                 logger,
                 scope,
@@ -290,14 +300,20 @@
         }
 
     @Test
-    fun dataEnabled_isEnabled() =
+    fun dataEnabled_initial_false() =
         runBlocking(IMMEDIATE) {
             whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
 
-            var latest: Boolean? = null
-            val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+            assertThat(underTest.dataEnabled.value).isFalse()
+        }
 
-            assertThat(latest).isTrue()
+    @Test
+    fun dataEnabled_isEnabled_true() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+            val job = underTest.dataEnabled.launchIn(this)
+
+            assertThat(underTest.dataEnabled.value).isTrue()
 
             job.cancel()
         }
@@ -306,10 +322,59 @@
     fun dataEnabled_isDisabled() =
         runBlocking(IMMEDIATE) {
             whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+            val job = underTest.dataEnabled.launchIn(this)
+
+            assertThat(underTest.dataEnabled.value).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isDefaultDataSubscription_isDefault() =
+        runBlocking(IMMEDIATE) {
+            connectionsRepo.setDefaultDataSubId(SUB_1_ID)
+
+            var latest: Boolean? = null
+            val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isDefaultDataSubscription_isNotDefault() =
+        runBlocking(IMMEDIATE) {
+            // Our subId is SUB_1_ID
+            connectionsRepo.setDefaultDataSubId(123)
+
+            var latest: Boolean? = null
+            val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
+        runBlocking(IMMEDIATE) {
+            val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
 
             var latest: Boolean? = null
             val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
 
+            // We don't read the setting directly, we query telephony when changes happen
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+            globalSettings.putInt(subIdSettingName, 0)
+            assertThat(latest).isFalse()
+
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+            globalSettings.putInt(subIdSettingName, 1)
+            assertThat(latest).isTrue()
+
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+            globalSettings.putInt(subIdSettingName, 0)
             assertThat(latest).isFalse()
 
             job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
index 326e0d281..a953a3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
@@ -16,26 +16,33 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
+import com.android.internal.telephony.PhoneConstants
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
@@ -43,7 +50,6 @@
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentMatchers
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -54,32 +60,26 @@
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionsRepositoryImpl
 
+    @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
 
     private val scope = CoroutineScope(IMMEDIATE)
+    private val globalSettings = FakeSettings()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(
-                broadcastDispatcher.broadcastFlow(
-                    any(),
-                    nullable(),
-                    ArgumentMatchers.anyInt(),
-                    nullable(),
-                )
-            )
-            .thenReturn(flowOf(Unit))
 
         underTest =
             MobileConnectionsRepositoryImpl(
+                connectivityManager,
                 subscriptionManager,
                 telephonyManager,
                 logger,
-                broadcastDispatcher,
+                fakeBroadcastDispatcher,
+                globalSettings,
                 context,
                 IMMEDIATE,
                 scope,
@@ -214,6 +214,139 @@
             job.cancel()
         }
 
+    @Test
+    fun testDefaultDataSubId_updatesOnBroadcast() =
+        runBlocking(IMMEDIATE) {
+            var latest: Int? = null
+            val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(
+                    context,
+                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+                )
+            }
+
+            assertThat(latest).isEqualTo(SUB_2_ID)
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(
+                    context,
+                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+                )
+            }
+
+            assertThat(latest).isEqualTo(SUB_1_ID)
+
+            job.cancel()
+        }
+
+    @Test
+    fun mobileConnectivity_default() {
+        assertThat(underTest.defaultMobileNetworkConnectivity.value)
+            .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+    }
+
+    @Test
+    fun mobileConnectivity_isConnected_isValidated() =
+        runBlocking(IMMEDIATE) {
+            val caps = createCapabilities(connected = true, validated = true)
+
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            assertThat(latest)
+                .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true))
+
+            job.cancel()
+        }
+
+    @Test
+    fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() =
+        runBlocking(IMMEDIATE) {
+            var produced = false
+            val job =
+                underTest.globalMobileDataSettingChangedEvent
+                    .onEach { produced = true }
+                    .launchIn(this)
+
+            assertThat(produced).isFalse()
+
+            globalSettings.putInt(Settings.Global.MOBILE_DATA, 0)
+
+            assertThat(produced).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun mobileConnectivity_isConnected_isNotValidated() =
+        runBlocking(IMMEDIATE) {
+            val caps = createCapabilities(connected = true, validated = false)
+
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            assertThat(latest)
+                .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false))
+
+            job.cancel()
+        }
+
+    @Test
+    fun mobileConnectivity_isNotConnected_isNotValidated() =
+        runBlocking(IMMEDIATE) {
+            val caps = createCapabilities(connected = false, validated = false)
+
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            assertThat(latest)
+                .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+
+            job.cancel()
+        }
+
+    /** In practice, I don't think this state can ever happen (!connected, validated) */
+    @Test
+    fun mobileConnectivity_isNotConnected_isValidated() =
+        runBlocking(IMMEDIATE) {
+            val caps = createCapabilities(connected = false, validated = true)
+
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            assertThat(latest).isEqualTo(MobileConnectivityModel(false, true))
+
+            job.cancel()
+        }
+
+    private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
+        mock<NetworkCapabilities>().also {
+            whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
+            whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated)
+        }
+
+    private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+        verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
     private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
         val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
         verify(subscriptionManager)
@@ -242,5 +375,8 @@
         private const val SUB_2_ID = 2
         private val SUB_2 =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+        private const val NET_ID = 123
+        private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 5611c44..3ae7d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -28,18 +28,23 @@
     private val _isEmergencyOnly = MutableStateFlow(false)
     override val isEmergencyOnly = _isEmergencyOnly
 
+    private val _isFailedConnection = MutableStateFlow(false)
+    override val isDefaultConnectionFailed = _isFailedConnection
+
+    override val isDataConnected = MutableStateFlow(true)
+
     private val _isDataEnabled = MutableStateFlow(true)
     override val isDataEnabled = _isDataEnabled
 
+    private val _isDefaultDataEnabled = MutableStateFlow(true)
+    override val isDefaultDataEnabled = _isDefaultDataEnabled
+
     private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
     override val level = _level
 
     private val _numberOfLevels = MutableStateFlow(4)
     override val numberOfLevels = _numberOfLevels
 
-    private val _cutOut = MutableStateFlow(false)
-    override val cutOut = _cutOut
-
     fun setIconGroup(group: SignalIcon.MobileIconGroup) {
         _iconGroup.value = group
     }
@@ -52,6 +57,14 @@
         _isDataEnabled.value = enabled
     }
 
+    fun setIsDefaultDataEnabled(disabled: Boolean) {
+        _isDefaultDataEnabled.value = disabled
+    }
+
+    fun setIsFailedConnection(failed: Boolean) {
+        _isFailedConnection.value = failed
+    }
+
     fun setLevel(level: Int) {
         _level.value = level
     }
@@ -59,8 +72,4 @@
     fun setNumberOfLevels(num: Int) {
         _numberOfLevels.value = num
     }
-
-    fun setCutOut(cutOut: Boolean) {
-        _cutOut.value = cutOut
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 2bd2286..061c3b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -26,8 +26,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
-    MobileIconsInteractor {
+class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
     val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
     val LTE_KEY = mobileMappings.toIconKey(LTE)
     val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -46,9 +45,14 @@
             FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
         )
 
+    override val isDefaultConnectionFailed = MutableStateFlow(false)
+
     private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
     override val filteredSubscriptions = _filteredSubscriptions
 
+    private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
+    override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+
     private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
     override val defaultMobileIconMapping = _defaultMobileIconMapping
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index ff44af4..7fc1c0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
@@ -34,6 +35,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -49,12 +51,17 @@
     private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
     private val connectionRepository = FakeMobileConnectionRepository()
 
+    private val scope = CoroutineScope(IMMEDIATE)
+
     @Before
     fun setUp() {
         underTest =
             MobileIconInteractorImpl(
+                scope,
+                mobileIconsInteractor.activeDataConnectionHasDataEnabled,
                 mobileIconsInteractor.defaultMobileIconMapping,
                 mobileIconsInteractor.defaultMobileIconGroup,
+                mobileIconsInteractor.isDefaultConnectionFailed,
                 mobileMappingsProxy,
                 connectionRepository,
             )
@@ -196,6 +203,66 @@
             job.cancel()
         }
 
+    @Test
+    fun test_isDefaultDataEnabled_matchesParent() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
+
+            mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+            assertThat(latest).isTrue()
+
+            mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun test_isDefaultConnectionFailed_matchedParent() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.isDefaultConnectionFailed.launchIn(this)
+
+            mobileIconsInteractor.isDefaultConnectionFailed.value = false
+            assertThat(underTest.isDefaultConnectionFailed.value).isFalse()
+
+            mobileIconsInteractor.isDefaultConnectionFailed.value = true
+            assertThat(underTest.isDefaultConnectionFailed.value).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun dataState_connected() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+            connectionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected)
+            )
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun dataState_notConnected() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+            connectionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected)
+            )
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 877ce0e..b56dcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
 import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
@@ -32,6 +34,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -168,6 +171,92 @@
             job.cancel()
         }
 
+    @Test
+    fun activeDataConnection_turnedOn() =
+        runBlocking(IMMEDIATE) {
+            CONNECTION_1.setDataEnabled(true)
+            var latest: Boolean? = null
+            val job =
+                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun activeDataConnection_turnedOff() =
+        runBlocking(IMMEDIATE) {
+            CONNECTION_1.setDataEnabled(true)
+            var latest: Boolean? = null
+            val job =
+                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+            CONNECTION_1.setDataEnabled(false)
+            yield()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun activeDataConnection_invalidSubId() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job =
+                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
+            yield()
+
+            // An invalid active subId should tell us that data is off
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun failedConnection_connected_validated_notFailed() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
+            yield()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun failedConnection_notConnected_notValidated_notFailed() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false))
+            yield()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun failedConnection_connected_notValidated_failed() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false))
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index ce0f33f..d4c2c3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -46,10 +46,12 @@
         MockitoAnnotations.initMocks(this)
         interactor.apply {
             setLevel(1)
-            setCutOut(false)
+            setIsDefaultDataEnabled(true)
+            setIsFailedConnection(false)
             setIconGroup(THREE_G)
             setIsEmergencyOnly(false)
             setNumberOfLevels(4)
+            isDataConnected.value = true
         }
         underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
     }
@@ -59,8 +61,23 @@
         runBlocking(IMMEDIATE) {
             var latest: Int? = null
             val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+            val expected = defaultSignal()
 
-            assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+            assertThat(latest).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun iconId_cutout_whenDefaultDataDisabled() =
+        runBlocking(IMMEDIATE) {
+            interactor.setIsDefaultDataEnabled(false)
+
+            var latest: Int? = null
+            val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+            val expected = defaultSignal(level = 1, connected = false)
+
+            assertThat(latest).isEqualTo(expected)
 
             job.cancel()
         }
@@ -97,6 +114,44 @@
         }
 
     @Test
+    fun networkType_nullWhenFailedConnection() =
+        runBlocking(IMMEDIATE) {
+            interactor.setIconGroup(THREE_G)
+            interactor.setIsDataEnabled(true)
+            interactor.setIsFailedConnection(true)
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun networkType_nullWhenDataDisconnects() =
+        runBlocking(IMMEDIATE) {
+            val initial =
+                Icon.Resource(
+                    THREE_G.dataType,
+                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                )
+
+            interactor.setIconGroup(THREE_G)
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.setIconGroup(THREE_G)
+            assertThat(latest).isEqualTo(initial)
+
+            interactor.isDataConnected.value = false
+            yield()
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
     fun networkType_null_changeToDisabled() =
         runBlocking(IMMEDIATE) {
             val expected =
@@ -119,6 +174,14 @@
             job.cancel()
         }
 
+    /** Convenience constructor for these tests */
+    private fun defaultSignal(
+        level: Int = 1,
+        connected: Boolean = true,
+    ): Int {
+        return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+    }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SUB_1_ID = 1
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 11178db..627bd09 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import kotlinx.coroutines.flow.Flow
@@ -52,6 +53,12 @@
     private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
     override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
 
+    private val _isBouncerShowing = MutableStateFlow(false)
+    override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+
+    private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+    override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+
     override fun isKeyguardShowing(): Boolean {
         return _isKeyguardShowing.value
     }
diff --git a/services/art-profile b/services/art-profile
index 3e05078..b6398c0 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -41551,7 +41551,7 @@
 HPLcom/android/server/statusbar/StatusBarManagerService$1;->setNavigationBarLumaSamplingEnabled(IZ)V
 HSPLcom/android/server/statusbar/StatusBarManagerService$1;->setNotificationDelegate(Lcom/android/server/notification/NotificationDelegate;)V
 HPLcom/android/server/statusbar/StatusBarManagerService$1;->setTopAppHidesStatusBar(Z)V+]Lcom/android/internal/statusbar/IStatusBar;Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy;
-PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V
+PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
 HPLcom/android/server/statusbar/StatusBarManagerService$1;->setWindowState(III)V
 PLcom/android/server/statusbar/StatusBarManagerService$1;->showChargingAnimation(I)V
 PLcom/android/server/statusbar/StatusBarManagerService$1;->showRecentApps(Z)V
@@ -41677,6 +41677,11 @@
 PLcom/android/server/statusbar/StatusBarManagerService;->setIcon(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V
 HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V
 HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V
+PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
+PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
+HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V+]Landroid/util/ArrayMap;Landroid/util/ArrayMap;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService;
+HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V+]Landroid/os/Handler;Landroid/os/Handler;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService;
+PLcom/android/server/statusbar/StatusBarManagerService;->setNavBarMode(I)V
 PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V
 PLcom/android/server/statusbar/StatusBarManagerService;->showAuthenticationDialog(Landroid/hardware/biometrics/PromptInfo;Landroid/hardware/biometrics/IBiometricSysuiReceiver;[IZZIJLjava/lang/String;JI)V
 PLcom/android/server/statusbar/StatusBarManagerService;->suppressAmbientDisplay(Z)V
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index cee78e0..34787a3 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -728,7 +728,7 @@
             if (oldList != null && adding != null) {
                 adding.removeAll(oldList);
             }
-
+            addingCount = CollectionUtils.size(adding);
             EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, addingCount);
             for (int i = 0; i < addingCount; i++) {
                 String packageName = adding.valueAt(i);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index fa52ac9..8055afc 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -26,9 +26,15 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.InstallSourceInfo;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -39,13 +45,19 @@
 import android.os.SystemProperties;
 import android.util.PackageUtils;
 import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.apk.ApkSigningBlockUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
 
+import libcore.util.HexEncoding;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -151,6 +163,117 @@
                     return 0;
                 }
 
+                private void printPackageMeasurements(PackageInfo packageInfo,
+                                                      final PrintWriter pw) {
+                    pw.print(mBinaryHashes.get(packageInfo.packageName) + ",");
+                    // TODO: To be moved to somewhere more suitable
+                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+                    ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
+                            ApkSignatureVerifier.verifySignaturesInternal(input,
+                                    packageInfo.applicationInfo.sourceDir,
+                                    SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, false);
+                    if (parseResult.isError()) {
+                        pw.println("ERROR: Failed to compute package content digest for "
+                                + packageInfo.applicationInfo.sourceDir + "due to: "
+                                + parseResult.getErrorMessage());
+                    } else {
+                        ApkSignatureVerifier.SigningDetailsWithDigests signingDetails =
+                                parseResult.getResult();
+                        Map<Integer, byte[]> contentDigests = signingDetails.contentDigests;
+                        for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+                            Integer algorithmId = entry.getKey();
+                            byte[] cDigest = entry.getValue();
+                            //pw.print("Content digest algorithm: ");
+                            switch (algorithmId) {
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+                                    pw.print("CHUNKED_SHA256:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+                                    pw.print("CHUNKED_SHA512:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                                    pw.print("VERITY_CHUNKED_SHA256:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+                                    pw.print("SHA256:");
+                                    break;
+                                default:
+                                    pw.print("UNKNOWN:");
+                            }
+                            pw.print(HexEncoding.encodeToString(cDigest, false));
+                        }
+                    }
+                }
+
+                private void printPackageInstallationInfo(PackageInfo packageInfo,
+                                                          final PrintWriter pw) {
+                    pw.println("--- Package Installation Info ---");
+                    pw.println("Current install location: "
+                            + packageInfo.applicationInfo.sourceDir);
+                    if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
+                        String origPackageFilepath = getOriginalPreinstalledLocation(
+                                packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
+                        pw.println("|--> Package pre-installed location: " + origPackageFilepath);
+                        String digest = mBinaryHashes.get(origPackageFilepath);
+                        if (digest == null) {
+                            pw.println("ERROR finding SHA256-digest from cache...");
+                        } else {
+                            pw.println("|--> Pre-installed package SHA256-digest: " + digest);
+                        }
+                    }
+                    pw.println("First install time (ms): " + packageInfo.firstInstallTime);
+                    pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
+                    boolean isPreloaded = (packageInfo.firstInstallTime
+                            == packageInfo.lastUpdateTime);
+                    pw.println("Is preloaded: " + isPreloaded);
+
+                    InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+                            packageInfo.packageName);
+                    if (installSourceInfo == null) {
+                        pw.println("ERROR: Unable to obtain installSourceInfo of "
+                                + packageInfo.packageName);
+                    } else {
+                        pw.println("Installation initiated by: "
+                                + installSourceInfo.getInitiatingPackageName());
+                        pw.println("Installation done by: "
+                                + installSourceInfo.getInstallingPackageName());
+                        pw.println("Installation originating from: "
+                                + installSourceInfo.getOriginatingPackageName());
+                    }
+
+                    if (packageInfo.isApex) {
+                        pw.println("Is an active APEX: " + packageInfo.isActiveApex);
+                    }
+                }
+
+                private void printPackageSignerDetails(SigningInfo signerInfo,
+                                                       final PrintWriter pw) {
+                    if (signerInfo == null) {
+                        pw.println("ERROR: Package's signingInfo is null.");
+                        return;
+                    }
+                    pw.println("--- Package Signer Info ---");
+                    pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
+                    Signature[] packageSigners = signerInfo.getApkContentsSigners();
+                    for (Signature packageSigner : packageSigners) {
+                        byte[] packageSignerDigestBytes =
+                                PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
+                        String packageSignerDigestHextring =
+                                HexEncoding.encodeToString(packageSignerDigestBytes, false);
+                        pw.println("Signer cert's SHA256-digest: " + packageSignerDigestHextring);
+                        try {
+                            PublicKey publicKey = packageSigner.getPublicKey();
+                            pw.println("Signing key algorithm: " + publicKey.getAlgorithm());
+                        } catch (CertificateException e) {
+                            Slog.e(TAG,
+                                    "Failed to obtain public key of signer for cert with hash: "
+                                    + packageSignerDigestHextring);
+                            e.printStackTrace();
+                        }
+                    }
+
+                }
+
                 private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
                     pw.println("--- Module Details ---");
                     pw.println("Module name: " + moduleInfo.getName());
@@ -190,28 +313,35 @@
                         return -1;
                     }
 
-                    pw.println("APEX Info:");
+                    if (!verbose) {
+                        pw.println("APEX Info [Format: package_name,package_version,"
+                                + "package_sha256_digest,"
+                                + "content_digest_algorithm:content_digest]:");
+                    }
                     for (PackageInfo packageInfo : getInstalledApexs()) {
+                        if (verbose) {
+                            pw.println("APEX Info [Format: package_name,package_version,"
+                                    + "package_sha256_digest,"
+                                    + "content_digest_algorithm:content_digest]:");
+                        }
                         String packageName = packageInfo.packageName;
-                        pw.println(packageName + ";"
-                                + packageInfo.getLongVersionCode() + ":"
-                                + mBinaryHashes.get(packageName).toLowerCase());
+                        pw.print(packageName + ","
+                                + packageInfo.getLongVersionCode() + ",");
+                        printPackageMeasurements(packageInfo, pw);
+                        pw.print("\n");
 
                         if (verbose) {
-                            pw.println("Install location: "
-                                    + packageInfo.applicationInfo.sourceDir);
-                            pw.println("Last Update Time (ms): " + packageInfo.lastUpdateTime);
-
                             ModuleInfo moduleInfo;
                             try {
                                 moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0);
+                                pw.println("Is a module: true");
+                                printModuleDetails(moduleInfo, pw);
                             } catch (PackageManager.NameNotFoundException e) {
-                                pw.println("Is A Module: False");
-                                pw.println("");
-                                continue;
+                                pw.println("Is a module: false");
                             }
-                            pw.println("Is A Module: True");
-                            printModuleDetails(moduleInfo, pw);
+
+                            printPackageInstallationInfo(packageInfo, pw);
+                            printPackageSignerDetails(packageInfo.signingInfo, pw);
                             pw.println("");
                         }
                     }
@@ -250,25 +380,37 @@
                         return -1;
                     }
 
-                    pw.println("Module Info:");
+                    if (!verbose) {
+                        pw.println("Module Info [Format: package_name,package_version,"
+                                + "package_sha256_digest,"
+                                + "content_digest_algorithm:content_digest]:");
+                    }
                     for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
                         String packageName = module.getPackageName();
+                        if (verbose) {
+                            pw.println("Module Info [Format: package_name,package_version,"
+                                    + "package_sha256_digest,"
+                                    + "content_digest_algorithm:content_digest]:");
+                        }
                         try {
                             PackageInfo packageInfo = pm.getPackageInfo(packageName,
-                                    PackageManager.MATCH_APEX);
-                            pw.println(packageInfo.packageName + ";"
-                                    + packageInfo.getLongVersionCode() + ":"
-                                    + mBinaryHashes.get(packageName).toLowerCase());
+                                    PackageManager.MATCH_APEX
+                                            | PackageManager.GET_SIGNING_CERTIFICATES);
+                            //pw.print("package:");
+                            pw.print(packageInfo.packageName + ",");
+                            pw.print(packageInfo.getLongVersionCode() + ",");
+                            printPackageMeasurements(packageInfo, pw);
+                            pw.print("\n");
 
                             if (verbose) {
-                                pw.println("Install location: "
-                                        + packageInfo.applicationInfo.sourceDir);
                                 printModuleDetails(module, pw);
+                                printPackageInstallationInfo(packageInfo, pw);
+                                printPackageSignerDetails(packageInfo.signingInfo, pw);
                                 pw.println("");
                             }
                         } catch (PackageManager.NameNotFoundException e) {
                             pw.println(packageName
-                                    + ";ERROR:Unable to find PackageInfo for this module.");
+                                    + ",ERROR:Unable to find PackageInfo for this module.");
                             if (verbose) {
                                 printModuleDetails(module, pw);
                                 pw.println("");
@@ -468,7 +610,8 @@
             return results;
         }
         List<PackageInfo> allPackages = pm.getInstalledPackages(
-                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
+                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX
+                        | PackageManager.GET_SIGNING_CERTIFICATES));
         if (allPackages == null) {
             Slog.e(TAG, "Error obtaining installed packages (including APEX)");
             return results;
@@ -478,6 +621,21 @@
         return results;
     }
 
+    @Nullable
+    private InstallSourceInfo getInstallSourceInfo(String packageName) {
+        PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            Slog.e(TAG, "Error obtaining an instance of PackageManager.");
+            return null;
+        }
+        try {
+            return pm.getInstallSourceInfo(packageName);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
 
     /**
      * Updates the internal data structure with the most current APEX measurements.
@@ -539,6 +697,14 @@
         return true;
     }
 
+    private String getOriginalPreinstalledLocation(String packageName,
+                                                   String currentInstalledLocation) {
+        if (currentInstalledLocation.contains("/decompressed/")) {
+            return "/system/apex/" + packageName + ".capex";
+        }
+        return "/system/apex" + packageName + "apex";
+    }
+
     private void doFreshBinaryMeasurements() {
         PackageManager pm = mContext.getPackageManager();
         Slog.d(TAG, "Obtained package manager");
@@ -567,6 +733,27 @@
             mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
         }
 
+        for (PackageInfo packageInfo : getInstalledApexs()) {
+            ApplicationInfo appInfo = packageInfo.applicationInfo;
+            if (!appInfo.sourceDir.startsWith("/data/")) {
+                continue;
+            }
+            String origInstallPath = getOriginalPreinstalledLocation(packageInfo.packageName,
+                                                                     appInfo.sourceDir);
+
+            // compute SHA256 for the orig /system APEXs
+            String sha256digest = PackageUtils.computeSha256DigestForLargeFile(origInstallPath,
+                                                                               largeFileBuffer);
+            if (sha256digest == null) {
+                Slog.e(TAG, String.format("Failed to compute SHA256 digest for file at %s",
+                                          origInstallPath));
+                mBinaryHashes.put(origInstallPath, BINARY_HASH_ERROR);
+            } else {
+                mBinaryHashes.put(origInstallPath, sha256digest);
+            }
+            // there'd be no entry into mBinaryLastUpdateTimes
+        }
+
         // Next, get all installed modules from PackageManager - skip over those APEXs we've
         // processed above
         for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java
index a9bdf06..ee6d808 100644
--- a/services/core/java/com/android/server/ConsumerIrService.java
+++ b/services/core/java/com/android/server/ConsumerIrService.java
@@ -92,6 +92,8 @@
     @Override
     @EnforcePermission(TRANSMIT_IR)
     public void transmit(String packageName, int carrierFrequency, int[] pattern) {
+        super.transmit_enforcePermission();
+
         long totalXmitTime = 0;
 
         for (int slice : pattern) {
@@ -128,6 +130,8 @@
     @Override
     @EnforcePermission(TRANSMIT_IR)
     public int[] getCarrierFrequencies() {
+        super.getCarrierFrequencies_enforcePermission();
+
         throwIfNoIrEmitter();
 
         synchronized(mHalLock) {
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index ce0e69c..27215b2 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -77,6 +77,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean startInstallation(String dsuSlot) throws RemoteException {
+        super.startInstallation_enforcePermission();
+
         IGsiService service = getGsiService();
         mGsiService = service;
         // priority from high to low: sysprop -> sdcard -> /data
@@ -124,6 +126,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public int createPartition(String name, long size, boolean readOnly) throws RemoteException {
+        super.createPartition_enforcePermission();
+
         IGsiService service = getGsiService();
         int status = service.createPartition(name, size, readOnly);
         if (status != IGsiService.INSTALL_OK) {
@@ -135,6 +139,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean closePartition() throws RemoteException {
+        super.closePartition_enforcePermission();
+
         IGsiService service = getGsiService();
         if (service.closePartition() != 0) {
             Slog.i(TAG, "Partition installation completes with error");
@@ -146,6 +152,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean finishInstallation() throws RemoteException {
+        super.finishInstallation_enforcePermission();
+
         IGsiService service = getGsiService();
         if (service.closeInstall() != 0) {
             Slog.i(TAG, "Failed to finish installation");
@@ -157,12 +165,16 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public GsiProgress getInstallationProgress() throws RemoteException {
+        super.getInstallationProgress_enforcePermission();
+
         return getGsiService().getInstallProgress();
     }
 
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean abort() throws RemoteException {
+        super.abort_enforcePermission();
+
         return getGsiService().cancelGsiInstall();
     }
 
@@ -183,12 +195,16 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean isEnabled() throws RemoteException {
+        super.isEnabled_enforcePermission();
+
         return getGsiService().isGsiEnabled();
     }
 
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean remove() throws RemoteException {
+        super.remove_enforcePermission();
+
         try {
             GsiServiceCallback callback = new GsiServiceCallback();
             synchronized (callback) {
@@ -205,6 +221,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException {
+        super.setEnable_enforcePermission();
+
         IGsiService gsiService = getGsiService();
         if (enable) {
             try {
@@ -229,6 +247,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean setAshmem(ParcelFileDescriptor ashmem, long size) {
+        super.setAshmem_enforcePermission();
+
         try {
             return getGsiService().setGsiAshmem(ashmem, size);
         } catch (RemoteException e) {
@@ -239,6 +259,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean submitFromAshmem(long size) {
+        super.submitFromAshmem_enforcePermission();
+
         try {
             return getGsiService().commitGsiChunkFromAshmem(size);
         } catch (RemoteException e) {
@@ -249,6 +271,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean getAvbPublicKey(AvbPublicKey dst) {
+        super.getAvbPublicKey_enforcePermission();
+
         try {
             return getGsiService().getAvbPublicKey(dst) == 0;
         } catch (RemoteException e) {
@@ -259,6 +283,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public long suggestScratchSize() throws RemoteException {
+        super.suggestScratchSize_enforcePermission();
+
         return getGsiService().suggestScratchSize();
     }
 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index d29e25c..5d54b6c 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -867,6 +867,8 @@
     public void shutdown() {
         // TODO: remove from aidl if nobody calls externally
 
+        super.shutdown_enforcePermission();
+
         Slog.i(TAG, "Shutting down");
     }
 
@@ -1207,6 +1209,8 @@
     @Override
     public boolean setDataSaverModeEnabled(boolean enable) {
 
+        super.setDataSaverModeEnabled_enforcePermission();
+
         if (DBG) Log.d(TAG, "setDataSaverMode: " + enable);
         synchronized (mQuotaLock) {
             if (mDataSaverMode == enable) {
@@ -1744,6 +1748,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
     @Override
     public boolean isNetworkRestricted(int uid) {
+        super.isNetworkRestricted_enforcePermission();
+
         return isNetworkRestrictedInternal(uid);
     }
 
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c1c9fbb..b56d1fc 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -231,7 +231,12 @@
             String namespaceToReset = namespaceIt.next();
             Properties properties = new Properties.Builder(namespaceToReset).build();
             try {
-                DeviceConfig.setProperties(properties);
+                if (!DeviceConfig.setProperties(properties)) {
+                    logCriticalInfo(Log.ERROR, "Failed to clear properties under "
+                            + namespaceToReset
+                            + ". Running `device_config get_sync_disabled_for_tests` will confirm"
+                            + " if config-bulk-update is enabled.");
+                }
             } catch (DeviceConfig.BadConfigException exception) {
                 logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
                         + " is already banned, skip reset.");
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index e915fa1..ff903a0 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -37,6 +37,8 @@
 
     @EnforcePermission(android.Manifest.permission.SERIAL_PORT)
     public String[] getSerialPorts() {
+        super.getSerialPorts_enforcePermission();
+
         ArrayList<String> ports = new ArrayList<String>();
         for (int i = 0; i < mSerialPorts.length; i++) {
             String path = mSerialPorts[i];
@@ -51,6 +53,8 @@
 
     @EnforcePermission(android.Manifest.permission.SERIAL_PORT)
     public ParcelFileDescriptor openSerialPort(String path) {
+        super.openSerialPort_enforcePermission();
+
         for (int i = 0; i < mSerialPorts.length; i++) {
             if (mSerialPorts[i].equals(path)) {
                 return native_open(path);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 72876f6..fcfee5b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1251,6 +1251,8 @@
     // Binder entry point for kicking off an immediate fstrim
     @Override
     public void runMaintenance() {
+        super.runMaintenance_enforcePermission();
+
         runIdleMaintenance(null);
     }
 
@@ -2167,6 +2169,8 @@
     @Override
     public void shutdown(final IStorageShutdownObserver observer) {
 
+        super.shutdown_enforcePermission();
+
         Slog.i(TAG, "Shutting down");
         mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
     }
@@ -2175,6 +2179,8 @@
     @Override
     public void mount(String volId) {
 
+        super.mount_enforcePermission();
+
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         if (isMountDisallowed(vol)) {
             throw new SecurityException("Mounting " + volId + " restricted by policy");
@@ -2243,6 +2249,8 @@
     @Override
     public void unmount(String volId) {
 
+        super.unmount_enforcePermission();
+
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         unmount(vol);
     }
@@ -2267,6 +2275,8 @@
     @Override
     public void format(String volId) {
 
+        super.format_enforcePermission();
+
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         final String fsUuid = vol.fsUuid;
         try {
@@ -2286,6 +2296,8 @@
     @Override
     public void benchmark(String volId, IVoldTaskListener listener) {
 
+        super.benchmark_enforcePermission();
+
         try {
             mVold.benchmark(volId, new IVoldTaskListener.Stub() {
                 @Override
@@ -2325,6 +2337,8 @@
     @Override
     public void partitionPublic(String diskId) {
 
+        super.partitionPublic_enforcePermission();
+
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
@@ -2337,6 +2351,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
     @Override
     public void partitionPrivate(String diskId) {
+        super.partitionPrivate_enforcePermission();
+
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
@@ -2351,6 +2367,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
     @Override
     public void partitionMixed(String diskId, int ratio) {
+        super.partitionMixed_enforcePermission();
+
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
@@ -2366,6 +2384,8 @@
     @Override
     public void setVolumeNickname(String fsUuid, String nickname) {
 
+        super.setVolumeNickname_enforcePermission();
+
         Objects.requireNonNull(fsUuid);
         synchronized (mLock) {
             final VolumeRecord rec = mRecords.get(fsUuid);
@@ -2379,6 +2399,8 @@
     @Override
     public void setVolumeUserFlags(String fsUuid, int flags, int mask) {
 
+        super.setVolumeUserFlags_enforcePermission();
+
         Objects.requireNonNull(fsUuid);
         synchronized (mLock) {
             final VolumeRecord rec = mRecords.get(fsUuid);
@@ -2392,6 +2414,8 @@
     @Override
     public void forgetVolume(String fsUuid) {
 
+        super.forgetVolume_enforcePermission();
+
         Objects.requireNonNull(fsUuid);
 
         synchronized (mLock) {
@@ -2416,6 +2440,8 @@
     @Override
     public void forgetAllVolumes() {
 
+        super.forgetAllVolumes_enforcePermission();
+
         synchronized (mLock) {
             for (int i = 0; i < mRecords.size(); i++) {
                 final String fsUuid = mRecords.keyAt(i);
@@ -2448,6 +2474,8 @@
     @Override
     public void fstrim(int flags, IVoldTaskListener listener) {
 
+        super.fstrim_enforcePermission();
+
         try {
             // Block based checkpoint process runs fstrim. So, if checkpoint is in progress
             // (first boot after OTA), We skip idle maintenance and make sure the last
@@ -2742,6 +2770,8 @@
     @Override
     public void setDebugFlags(int flags, int mask) {
 
+        super.setDebugFlags_enforcePermission();
+
         if ((mask & (StorageManager.DEBUG_ADOPTABLE_FORCE_ON
                 | StorageManager.DEBUG_ADOPTABLE_FORCE_OFF)) != 0) {
             final String value;
@@ -2812,6 +2842,8 @@
     @Override
     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
 
+        super.setPrimaryStorageUuid_enforcePermission();
+
         final VolumeInfo from;
         final VolumeInfo to;
 
@@ -3020,6 +3052,8 @@
      */
     @Override
     public boolean needsCheckpoint() throws RemoteException {
+        super.needsCheckpoint_enforcePermission();
+
         return mVold.needsCheckpoint();
     }
 
@@ -3040,6 +3074,8 @@
     @Override
     public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
 
+        super.createUserKey_enforcePermission();
+
         try {
             mVold.createUserKey(userId, serialNumber, ephemeral);
             // New keys are always unlocked.
@@ -3055,6 +3091,8 @@
     @Override
     public void destroyUserKey(int userId) {
 
+        super.destroyUserKey_enforcePermission();
+
         try {
             mVold.destroyUserKey(userId);
             // Destroying a key also locks it.
@@ -3070,6 +3108,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
     public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException {
+        super.setUserKeyProtection_enforcePermission();
+
         mVold.setUserKeyProtection(userId, HexDump.toHexString(secret));
     }
 
@@ -3078,6 +3118,8 @@
     @Override
     public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret)
         throws RemoteException {
+        super.unlockUserKey_enforcePermission();
+
         if (StorageManager.isFileEncrypted()) {
             mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
         }
@@ -3090,6 +3132,8 @@
     @Override
     public void lockUserKey(int userId) {
         //  Do not lock user 0 data for headless system user
+        super.lockUserKey_enforcePermission();
+
         if (userId == UserHandle.USER_SYSTEM
                 && UserManager.isHeadlessSystemUserMode()) {
             throw new IllegalArgumentException("Headless system user data cannot be locked..");
@@ -3153,6 +3197,8 @@
     @Override
     public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
 
+        super.prepareUserStorage_enforcePermission();
+
         try {
             prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags);
         } catch (Exception e) {
@@ -3196,6 +3242,8 @@
     @Override
     public void destroyUserStorage(String volumeUuid, int userId, int flags) {
 
+        super.destroyUserStorage_enforcePermission();
+
         try {
             mVold.destroyUserStorage(volumeUuid, userId, flags);
         } catch (Exception e) {
@@ -4247,6 +4295,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
     @Override
     public int getExternalStorageMountMode(int uid, String packageName) {
+        super.getExternalStorageMountMode_enforcePermission();
+
         return mStorageManagerInternal.getExternalStorageMountMode(uid, packageName);
     }
 
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index 9323d95..7f24c52 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -32,6 +32,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -192,10 +193,12 @@
     private static void dumpStackTraces() {
         final ArrayList<Integer> pids = new ArrayList<>();
         pids.add(Process.myPid());
-        ActivityManagerService.dumpStackTraces(pids, /* processCpuTracker= */null,
-                /* lastPids= */null, Watchdog.getInterestingNativePids(),
+        ActivityManagerService.dumpStackTraces(pids,
+                /* processCpuTracker= */null, /* lastPids= */null,
+                CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()),
                 /* logExceptionCreatingFile= */null, /* subject= */null,
-                /* criticalEventSection= */null, /* latencyTracker= */null);
+                /* criticalEventSection= */null, Runnable::run,
+                /* latencyTracker= */null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 83d86cd..953e850 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -371,6 +371,7 @@
             // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
             // called onUserSwitching(), so calling it before onUserStarting() make it more
             // consistent with that
+            EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
             onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
         }
         onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
@@ -381,14 +382,30 @@
      *
      * <p><b>NOTE: </b>this method should only be called when a user that is already running become
      * visible; if the user is starting visible, callers should call
-     * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead
+     * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead.
      */
     public void onUserVisible(@UserIdInt int userId) {
-        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId);
+        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
         onUser(USER_VISIBLE, userId);
     }
 
     /**
+     * Updates the visibility of the system user.
+     *
+     * <p>Since the system user never stops, this method must be called when it's switched from / to
+     * foreground.
+     */
+    public void onSystemUserVisibilityChanged(boolean visible) {
+        int userId = UserHandle.USER_SYSTEM;
+        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
+        if (visible) {
+            onUser(USER_VISIBLE, userId);
+        } else {
+            onUser(USER_INVISIBLE, userId);
+        }
+    }
+
+    /**
      * Unlocks the given user.
      */
     public void onUserUnlocking(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b00dec0..6eeb906 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -49,7 +49,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.ProcessCpuTracker;
@@ -75,6 +75,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -894,8 +895,9 @@
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
         StringWriter tracesFileException = new StringWriter();
         final File stack = ActivityManagerService.dumpStackTraces(
-                pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids(),
-                tracesFileException, subject, criticalEvents, /* latencyTracker= */null);
+                pids, processCpuTracker, new SparseBooleanArray(),
+                CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException,
+                subject, criticalEvents, Runnable::run, /* latencyTracker= */null);
         // Give some extra time to make sure the stack traces get written.
         // The system's been hanging for a whlie, another second or two won't hurt much.
         SystemClock.sleep(5000);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 062afe9..a17b5e2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -332,6 +332,7 @@
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -467,11 +468,15 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiFunction;
+import java.util.function.Supplier;
 
 public class ActivityManagerService extends IActivityManager.Stub
         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -3427,14 +3432,15 @@
      * @param lastPids of dalvik VM processes to dump stack traces for last
      * @param nativePids optional list of native pids to dump stack crawls
      * @param logExceptionCreatingFile optional writer to which we log errors creating the file
+     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
      * @param latencyTracker the latency tracker instance of the current ANR.
      */
     public static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
-            ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
-            AnrLatencyTracker latencyTracker) {
-        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
-                logExceptionCreatingFile, null, null, null, latencyTracker);
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+                logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
     }
 
     /**
@@ -3445,14 +3451,17 @@
      * @param logExceptionCreatingFile optional writer to which we log errors creating the file
      * @param subject optional line related to the error
      * @param criticalEventSection optional lines containing recent critical events.
+     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
      * @param latencyTracker the latency tracker instance of the current ANR.
      */
     public static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
-            ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
-            String subject, String criticalEventSection, AnrLatencyTracker latencyTracker) {
-        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
-                logExceptionCreatingFile, null, subject, criticalEventSection, latencyTracker);
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+            String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
+            AnrLatencyTracker latencyTracker) {
+        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+                logExceptionCreatingFile, null, subject, criticalEventSection,
+                auxiliaryTaskExecutor, latencyTracker);
     }
 
     /**
@@ -3460,49 +3469,26 @@
      *                        of the very first pid to be dumped.
      */
     /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
-            ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
             AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
-            AnrLatencyTracker latencyTracker) {
+            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
         try {
+
             if (latencyTracker != null) {
                 latencyTracker.dumpStackTracesStarted();
             }
-            ArrayList<Integer> extraPids = null;
 
-            Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);
+            Slog.i(TAG, "dumpStackTraces pids=" + lastPids);
 
             // Measure CPU usage as soon as we're called in order to get a realistic sampling
             // of the top users at the time of the request.
-            if (processCpuTracker != null) {
-                if (latencyTracker != null) {
-                    latencyTracker.processCpuTrackerMethodsCalled();
-                }
-                processCpuTracker.init();
-                try {
-                    Thread.sleep(200);
-                } catch (InterruptedException ignored) {
-                }
-
-                processCpuTracker.update();
-
-                // We'll take the stack crawls of just the top apps using CPU.
-                final int workingStatsNumber = processCpuTracker.countWorkingStats();
-                extraPids = new ArrayList<>();
-                for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
-                    ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
-                    if (lastPids.indexOfKey(stats.pid) >= 0) {
-                        if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
-
-                        extraPids.add(stats.pid);
-                    } else {
-                        Slog.i(TAG, "Skipping next CPU consuming process, not a java proc: "
-                                + stats.pid);
-                    }
-                }
-                if (latencyTracker != null) {
-                    latencyTracker.processCpuTrackerMethodsReturned();
-                }
+            Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
+                    ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
+            Future<ArrayList<Integer>> extraPidsFuture = null;
+            if (extraPidsSupplier != null) {
+                extraPidsFuture =
+                        CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
             }
 
             final File tracesDir = new File(ANR_TRACE_DIR);
@@ -3536,7 +3522,8 @@
             }
 
             long firstPidEndPos = dumpStackTraces(
-                    tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids, latencyTracker);
+                    tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
+                    extraPidsFuture, latencyTracker);
             if (firstPidEndOffset != null) {
                 firstPidEndOffset.set(firstPidEndPos);
             }
@@ -3554,6 +3541,42 @@
     private static SimpleDateFormat sAnrFileDateFormat;
     static final String ANR_FILE_PREFIX = "anr_";
 
+    private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
+            SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
+        if (latencyTracker != null) {
+            latencyTracker.processCpuTrackerMethodsCalled();
+        }
+        ArrayList<Integer> extraPids = new ArrayList<>();
+        processCpuTracker.init();
+        try {
+            Thread.sleep(200);
+        } catch (InterruptedException ignored) {
+        }
+
+        processCpuTracker.update();
+
+        // We'll take the stack crawls of just the top apps using CPU.
+        final int workingStatsNumber = processCpuTracker.countWorkingStats();
+        for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
+            ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+            if (lastPids.indexOfKey(stats.pid) >= 0) {
+                if (DEBUG_ANR) {
+                    Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                }
+
+                extraPids.add(stats.pid);
+            } else {
+                Slog.i(TAG,
+                        "Skipping next CPU consuming process, not a java proc: "
+                        + stats.pid);
+            }
+        }
+        if (latencyTracker != null) {
+            latencyTracker.processCpuTrackerMethodsReturned();
+        }
+        return extraPids;
+    }
+
     private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
         if (sAnrFileDateFormat == null) {
             sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
@@ -3660,8 +3683,8 @@
      * @return The end offset of the trace of the very first PID
      */
     public static long dumpStackTraces(String tracesFile,
-            ArrayList<Integer> firstPids, ArrayList<Integer> nativePids,
-            ArrayList<Integer> extraPids, AnrLatencyTracker latencyTracker) {
+            ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
+            Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) {
 
         Slog.i(TAG, "Dumping to " + tracesFile);
 
@@ -3682,6 +3705,7 @@
             if (latencyTracker != null) {
                 latencyTracker.dumpingFirstPidsStarted();
             }
+
             int num = firstPids.size();
             for (int i = 0; i < num; i++) {
                 final int pid = firstPids.get(i);
@@ -3723,6 +3747,10 @@
         }
 
         // Next collect the stacks of the native pids
+        ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");
+
+        Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);
+
         if (nativePids != null) {
             if (latencyTracker != null) {
                 latencyTracker.dumpingNativePidsStarted();
@@ -3758,6 +3786,19 @@
         }
 
         // Lastly, dump stacks for all extra PIDs from the CPU tracker.
+        ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");
+
+        if (extraPidsFuture != null) {
+            try {
+                extraPids = extraPidsFuture.get();
+            } catch (ExecutionException e) {
+                Slog.w(TAG, "Failed to collect extra pids", e.getCause());
+            } catch (InterruptedException e) {
+                Slog.w(TAG, "Interrupted while collecting extra pids", e);
+            }
+        }
+        Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);
+
         if (extraPids != null) {
             if (latencyTracker != null) {
                 latencyTracker.dumpingExtraPidsStarted();
@@ -3793,6 +3834,24 @@
         return firstPidEnd;
     }
 
+    private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
+            String logName) {
+
+        ArrayList<Integer> pids = null;
+
+        if (pidsFuture == null) {
+            return pids;
+        }
+        try {
+            pids = pidsFuture.get();
+        } catch (ExecutionException e) {
+            Slog.w(TAG, "Failed to collect " + logName, e.getCause());
+        } catch (InterruptedException e) {
+            Slog.w(TAG, "Interrupted while collecting " + logName , e);
+        }
+        return pids;
+    }
+
     @Override
     public boolean clearApplicationUserData(final String packageName, boolean keepState,
             final IPackageDataObserver observer, int userId) {
@@ -14664,6 +14723,15 @@
         mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
     }
 
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+        final String callerPackage = info != null ? info.packageName : original.callerPackage;
+        if (callerPackage != null) {
+            mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+                    original.callingUid, 0, callerPackage).sendToTarget();
+        }
+    }
+
     final Intent verifyBroadcastLocked(Intent intent) {
         // Refuse possible leaked file descriptors
         if (intent != null && intent.hasFileDescriptors() == true) {
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 6de4118..71c80ea 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -25,10 +25,15 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.TimeoutRecord;
 import com.android.server.wm.WindowProcessController;
 
 import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -51,6 +56,14 @@
      */
     private static final long CONSECUTIVE_ANR_TIME_MS = TimeUnit.MINUTES.toMillis(2);
 
+    /**
+     * The keep alive time for the threads in the helper threadpool executor
+    */
+    private static final int AUX_THREAD_KEEP_ALIVE_SECOND = 10;
+
+    private static final ThreadFactory sDefaultThreadFactory =  r ->
+            new Thread(r, "AnrAuxiliaryTaskExecutor");
+
     @GuardedBy("mAnrRecords")
     private final ArrayList<AnrRecord> mAnrRecords = new ArrayList<>();
     private final AtomicBoolean mRunning = new AtomicBoolean(false);
@@ -66,8 +79,18 @@
     @GuardedBy("mAnrRecords")
     private int mProcessingPid = -1;
 
+    private final ExecutorService mAuxiliaryTaskExecutor;
+
     AnrHelper(final ActivityManagerService service) {
+        this(service, new ThreadPoolExecutor(/* corePoolSize= */ 0, /* maximumPoolSize= */ 1,
+                /* keepAliveTime= */ AUX_THREAD_KEEP_ALIVE_SECOND, TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(), sDefaultThreadFactory));
+    }
+
+    @VisibleForTesting
+    AnrHelper(ActivityManagerService service, ExecutorService auxExecutor) {
         mService = service;
+        mAuxiliaryTaskExecutor = auxExecutor;
     }
 
     void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) {
@@ -108,7 +131,8 @@
                 }
                 timeoutRecord.mLatencyTracker.anrRecordPlacingOnQueueWithSize(mAnrRecords.size());
                 mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
-                        parentShortComponentName, parentProcess, aboveSystem, timeoutRecord));
+                        parentShortComponentName, parentProcess, aboveSystem,
+                        mAuxiliaryTaskExecutor, timeoutRecord));
             }
             startAnrConsumerIfNeeded();
         } finally {
@@ -204,11 +228,12 @@
         final ApplicationInfo mAppInfo;
         final WindowProcessController mParentProcess;
         final boolean mAboveSystem;
+        final ExecutorService mAuxiliaryTaskExecutor;
         final long mTimestamp = SystemClock.uptimeMillis();
         AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
                 ApplicationInfo aInfo, String parentShortComponentName,
                 WindowProcessController parentProcess, boolean aboveSystem,
-                TimeoutRecord timeoutRecord) {
+                ExecutorService auxiliaryTaskExecutor, TimeoutRecord timeoutRecord) {
             mApp = anrProcess;
             mPid = anrProcess.mPid;
             mActivityShortComponentName = activityShortComponentName;
@@ -217,6 +242,7 @@
             mAppInfo = aInfo;
             mParentProcess = parentProcess;
             mAboveSystem = aboveSystem;
+            mAuxiliaryTaskExecutor = auxiliaryTaskExecutor;
         }
 
         void appNotResponding(boolean onlyDumpSelf) {
@@ -224,7 +250,7 @@
                 mTimeoutRecord.mLatencyTracker.anrProcessingStarted();
                 mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
                         mParentShortComponentName, mParentProcess, mAboveSystem,
-                        mTimeoutRecord, onlyDumpSelf);
+                        mTimeoutRecord, mAuxiliaryTaskExecutor, onlyDumpSelf);
             } finally {
                 mTimeoutRecord.mLatencyTracker.anrProcessingEnded();
             }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 2e12309..c994f13 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -135,14 +135,6 @@
     private int mActiveIndex;
 
     /**
-     * When defined, the receiver actively being dispatched into this process
-     * was considered "blocked" until at least the given count of other
-     * receivers have reached a terminal state; typically used for ordered
-     * broadcasts and priority traunches.
-     */
-    private int mActiveBlockedUntilTerminalCount;
-
-    /**
      * Count of {@link #mActive} broadcasts that have been dispatched since this
      * queue was last idle.
      */
@@ -206,15 +198,11 @@
      * given count of other receivers have reached a terminal state; typically
      * used for ordered broadcasts and priority traunches.
      */
-    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
-            int blockedUntilTerminalCount) {
+    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
         if (record.isReplacePending()) {
-            boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex,
-                    blockedUntilTerminalCount)
-                    || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex,
-                            blockedUntilTerminalCount)
-                    || replaceBroadcastInQueue(mPendingOffload, record, recordIndex,
-                            blockedUntilTerminalCount);
+            boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex)
+                    || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex)
+                    || replaceBroadcastInQueue(mPendingOffload, record, recordIndex);
             if (didReplace) {
                 return;
             }
@@ -225,7 +213,6 @@
         SomeArgs newBroadcastArgs = SomeArgs.obtain();
         newBroadcastArgs.arg1 = record;
         newBroadcastArgs.argi1 = recordIndex;
-        newBroadcastArgs.argi2 = blockedUntilTerminalCount;
 
         // Cross-broadcast prioritization policy:  some broadcasts might warrant being
         // issued ahead of others that are already pending, for example if this new
@@ -244,7 +231,7 @@
      * {@code false} otherwise.
      */
     private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
-            @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
+            @NonNull BroadcastRecord record, int recordIndex) {
         final Iterator<SomeArgs> it = queue.descendingIterator();
         final Object receiver = record.receivers.get(recordIndex);
         while (it.hasNext()) {
@@ -259,7 +246,6 @@
                 // Exact match found; perform in-place swap
                 args.arg1 = record;
                 args.argi1 = recordIndex;
-                args.argi2 = blockedUntilTerminalCount;
                 onBroadcastDequeued(testRecord, testRecordIndex);
                 onBroadcastEnqueued(record, recordIndex);
                 return true;
@@ -411,7 +397,6 @@
         final SomeArgs next = removeNextBroadcast();
         mActive = (BroadcastRecord) next.arg1;
         mActiveIndex = next.argi1;
-        mActiveBlockedUntilTerminalCount = next.argi2;
         mActiveCountSinceIdle++;
         mActiveViaColdStart = false;
         next.recycle();
@@ -424,7 +409,6 @@
     public void makeActiveIdle() {
         mActive = null;
         mActiveIndex = 0;
-        mActiveBlockedUntilTerminalCount = -1;
         mActiveCountSinceIdle = 0;
         mActiveViaColdStart = false;
         invalidateRunnableAt();
@@ -705,7 +689,7 @@
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
-            final int blockedUntilTerminalCount = next.argi2;
+            final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
             final long runnableAt = r.enqueueTime;
 
             // We might be blocked waiting for other receivers to finish,
@@ -871,19 +855,19 @@
         pw.println();
         pw.increaseIndent();
         if (mActive != null) {
-            dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
+            dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
         }
         for (SomeArgs args : mPendingUrgent) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            dumpRecord("URGENT", now, pw, r, args.argi1, args.argi2);
+            dumpRecord("URGENT", now, pw, r, args.argi1);
         }
         for (SomeArgs args : mPending) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            dumpRecord(null, now, pw, r, args.argi1, args.argi2);
+            dumpRecord(null, now, pw, r, args.argi1);
         }
         for (SomeArgs args : mPendingOffload) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            dumpRecord("OFFLOAD", now, pw, r, args.argi1, args.argi2);
+            dumpRecord("OFFLOAD", now, pw, r, args.argi1);
         }
         pw.decreaseIndent();
         pw.println();
@@ -891,8 +875,7 @@
 
     @NeverCompile
     private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
-            @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex,
-            int blockedUntilTerminalCount) {
+            @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
         TimeUtils.formatDuration(record.enqueueTime, now, pw);
         pw.print(' ');
         pw.println(record.toShortString());
@@ -918,6 +901,7 @@
             pw.print(info.activityInfo.name);
         }
         pw.println();
+        final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
         if (blockedUntilTerminalCount != -1) {
             pw.print("    blocked until ");
             pw.print(blockedUntilTerminalCount);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e0fab2c..153ad1e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -77,6 +77,18 @@
         }
     }
 
+    static void checkState(boolean expression, @NonNull String msg) {
+        if (!expression) {
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    static void checkStateWtf(boolean expression, @NonNull String msg) {
+        if (!expression) {
+            Slog.wtf(TAG, new IllegalStateException(msg));
+        }
+    }
+
     static int traceBegin(@NonNull String methodName) {
         final int cookie = methodName.hashCode();
         Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index ffc54d9..454b284 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1690,13 +1690,7 @@
                 System.identityHashCode(original));
         }
 
-        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
-        final String callerPackage = info != null ? info.packageName : original.callerPackage;
-        if (callerPackage != null) {
-            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
-                    original.callingUid, 0, callerPackage).sendToTarget();
-        }
-
+        mService.notifyBroadcastFinishedLocked(original);
         mHistory.addBroadcastToHistoryLocked(original);
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index af2a97e..c3839a9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -25,14 +25,12 @@
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
 import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
 import static com.android.server.am.BroadcastProcessQueue.reasonToString;
 import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
 import static com.android.server.am.BroadcastRecord.deliveryStateToString;
 import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
-import static com.android.server.am.BroadcastRecord.getReceiverPriority;
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
 import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
@@ -321,7 +319,6 @@
             return;
         }
 
-        final int cookie = traceBegin("updateRunnableList");
         final boolean wantQueue = queue.isRunnable();
         final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
                 || (queue.runnableAtNext != null);
@@ -348,8 +345,6 @@
         if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
             removeProcessQueue(queue.processName, queue.uid);
         }
-
-        traceEnd(cookie);
     }
 
     /**
@@ -592,36 +587,11 @@
         r.enqueueRealTime = SystemClock.elapsedRealtime();
         r.enqueueClockTime = System.currentTimeMillis();
 
-        int lastPriority = 0;
-        int lastPriorityIndex = 0;
-
         for (int i = 0; i < r.receivers.size(); i++) {
             final Object receiver = r.receivers.get(i);
             final BroadcastProcessQueue queue = getOrCreateProcessQueue(
                     getReceiverProcessName(receiver), getReceiverUid(receiver));
-
-            final int blockedUntilTerminalCount;
-            if (r.ordered) {
-                // When sending an ordered broadcast, we need to block this
-                // receiver until all previous receivers have terminated
-                blockedUntilTerminalCount = i;
-            } else if (r.prioritized) {
-                // When sending a prioritized broadcast, we only need to wait
-                // for the previous traunch of receivers to be terminated
-                final int thisPriority = getReceiverPriority(receiver);
-                if ((i == 0) || (thisPriority != lastPriority)) {
-                    lastPriority = thisPriority;
-                    lastPriorityIndex = i;
-                    blockedUntilTerminalCount = i;
-                } else {
-                    blockedUntilTerminalCount = lastPriorityIndex;
-                }
-            } else {
-                // Otherwise we don't need to block at all
-                blockedUntilTerminalCount = -1;
-            }
-
-            queue.enqueueOrReplaceBroadcast(r, i, blockedUntilTerminalCount);
+            queue.enqueueOrReplaceBroadcast(r, i);
             updateRunnableList(queue);
             enqueueUpdateRunningList();
         }
@@ -757,7 +727,7 @@
         }
         final Intent receiverIntent = r.getReceiverIntent(receiver);
         if (receiverIntent == null) {
-            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
             return;
         }
 
@@ -917,9 +887,12 @@
 
     private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
             @DeliveryState int deliveryState, @NonNull String reason) {
-        final int cookie = traceBegin("finishReceiver");
-        checkState(queue.isActive(), "isActive");
+        if (!queue.isActive()) {
+            logw("Ignoring finish; no active broadcast for " + queue);
+            return false;
+        }
 
+        final int cookie = traceBegin("finishReceiver");
         final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
         final int index = queue.getActiveIndex();
@@ -1429,6 +1402,7 @@
 
         final boolean recordFinished = (r.terminalCount == r.receivers.size());
         if (recordFinished) {
+            mService.notifyBroadcastFinishedLocked(r);
             mHistory.addBroadcastToHistoryLocked(r);
 
             r.finishTime = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 65f9b9b..6ea2dee 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -96,6 +96,7 @@
     final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
     final @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
     final @DeliveryState int[] delivery;   // delivery state of each receiver
+    final int[] blockedUntilTerminalCount; // blocked until count of each receiver
     @Nullable ProcessRecord resultToApp; // who receives final result if non-null
     @Nullable IIntentReceiver resultTo; // who receives final result if non-null
     boolean deferred;
@@ -375,6 +376,7 @@
         options = _options;
         receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
         delivery = new int[_receivers != null ? _receivers.size() : 0];
+        blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
         terminalTime = new long[delivery.length];
         resultToApp = _resultToApp;
@@ -385,7 +387,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
-        prioritized = isPrioritized(receivers);
+        prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
@@ -427,6 +429,7 @@
         options = from.options;
         receivers = from.receivers;
         delivery = from.delivery;
+        blockedUntilTerminalCount = from.blockedUntilTerminalCount;
         scheduledTime = from.scheduledTime;
         terminalTime = from.terminalTime;
         resultToApp = from.resultToApp;
@@ -690,22 +693,60 @@
     }
 
     /**
-     * Return if given receivers list has more than one traunch of priorities.
+     * Determine if the result of {@link #calculateBlockedUntilTerminalCount}
+     * has prioritized tranches of receivers.
      */
     @VisibleForTesting
-    static boolean isPrioritized(@NonNull List<Object> receivers) {
-        int firstPriority = 0;
-        for (int i = 0; i < receivers.size(); i++) {
-            final int thisPriority = getReceiverPriority(receivers.get(i));
-            if (i == 0) {
-                firstPriority = thisPriority;
-            } else if (thisPriority != firstPriority) {
-                return true;
-            }
-        }
-        return false;
+    static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+            boolean ordered) {
+        return !ordered && (blockedUntilTerminalCount.length > 0)
+                && (blockedUntilTerminalCount[0] != -1);
     }
 
+    /**
+     * Calculate the {@link #terminalCount} that each receiver should be
+     * considered blocked until.
+     * <p>
+     * For example, in an ordered broadcast, receiver {@code N} is blocked until
+     * receiver {@code N-1} reaches a terminal state. Similarly, in a
+     * prioritized broadcast, receiver {@code N} is blocked until all receivers
+     * of a higher priority reach a terminal state.
+     * <p>
+     * When there are no terminal count constraints, the blocked value for each
+     * receiver is {@code -1}.
+     */
+    @VisibleForTesting
+    static @NonNull int[] calculateBlockedUntilTerminalCount(
+            @NonNull List<Object> receivers, boolean ordered) {
+        final int N = receivers.size();
+        final int[] blockedUntilTerminalCount = new int[N];
+        int lastPriority = 0;
+        int lastPriorityIndex = 0;
+        for (int i = 0; i < N; i++) {
+            if (ordered) {
+                // When sending an ordered broadcast, we need to block this
+                // receiver until all previous receivers have terminated
+                blockedUntilTerminalCount[i] = i;
+            } else {
+                // When sending a prioritized broadcast, we only need to wait
+                // for the previous tranche of receivers to be terminated
+                final int thisPriority = getReceiverPriority(receivers.get(i));
+                if ((i == 0) || (thisPriority != lastPriority)) {
+                    lastPriority = thisPriority;
+                    lastPriorityIndex = i;
+                    blockedUntilTerminalCount[i] = i;
+                } else {
+                    blockedUntilTerminalCount[i] = lastPriorityIndex;
+                }
+            }
+        }
+        // If the entire list is in the same priority tranche, mark as -1 to
+        // indicate that none of them need to wait
+        if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
+            Arrays.fill(blockedUntilTerminalCount, -1);
+        }
+        return blockedUntilTerminalCount;
+    }
 
     static int getReceiverUid(@NonNull Object receiver) {
         if (receiver instanceof BroadcastFilter) {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index dec8b62..60e6754 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -116,7 +116,7 @@
 30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
 30087 ssm_user_stopped (userId|1|5)
 30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
-30089 ssm_user_visible (userId|1|5)
+30089 ssm_user_visibility_changed (userId|1|5),(visible|1)
 
 # Foreground service start/stop events.
 30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index f461f3d..68d906b 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -44,7 +44,7 @@
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
@@ -62,8 +62,12 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicLong;
 
+
 /**
  * The error state of the process, such as if it's crashing/ANR etc.
  */
@@ -259,12 +263,13 @@
     void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
             String parentShortComponentName, WindowProcessController parentProcess,
             boolean aboveSystem, TimeoutRecord timeoutRecord,
-            boolean onlyDumpSelf) {
+            ExecutorService auxiliaryTaskExecutor, boolean onlyDumpSelf) {
         String annotation = timeoutRecord.mReason;
         AnrLatencyTracker latencyTracker = timeoutRecord.mLatencyTracker;
+        Future<?> updateCpuStatsNowFirstCall = null;
 
         ArrayList<Integer> firstPids = new ArrayList<>(5);
-        SparseArray<Boolean> lastPids = new SparseArray<>(20);
+        SparseBooleanArray lastPids = new SparseBooleanArray(20);
 
         mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
             latencyTracker.waitingOnAMSLockStarted();
@@ -277,10 +282,15 @@
         });
 
         long anrTime = SystemClock.uptimeMillis();
+
         if (isMonitorCpuUsage()) {
-            latencyTracker.updateCpuStatsNowCalled();
-            mService.updateCpuStatsNow();
-            latencyTracker.updateCpuStatsNowReturned();
+            updateCpuStatsNowFirstCall = auxiliaryTaskExecutor.submit(
+                    () -> {
+                    latencyTracker.updateCpuStatsNowCalled();
+                    mService.updateCpuStatsNow();
+                    latencyTracker.updateCpuStatsNowReturned();
+                });
+
         }
 
         final boolean isSilentAnr;
@@ -369,7 +379,7 @@
                                 firstPids.add(myPid);
                                 if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
                             } else {
-                                lastPids.put(myPid, Boolean.TRUE);
+                                lastPids.put(myPid, true);
                                 if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
                             }
                         }
@@ -432,30 +442,40 @@
         report.append(currentPsiState);
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
 
-        latencyTracker.nativePidCollectionStarted();
-        // don't dump native PIDs for background ANRs unless it is the process of interest
-        String[] nativeProcs = null;
-        if (isSilentAnr || onlyDumpSelf) {
-            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
-                if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
-                    nativeProcs = new String[] { mApp.processName };
-                    break;
-                }
-            }
-        } else {
-            nativeProcs = NATIVE_STACKS_OF_INTEREST;
-        }
+        // We push the native pids collection task to the helper thread through
+        // the Anr auxiliary task executor, and wait on it later after dumping the first pids
+        Future<ArrayList<Integer>> nativePidsFuture =
+                auxiliaryTaskExecutor.submit(
+                    () -> {
+                        latencyTracker.nativePidCollectionStarted();
+                        // don't dump native PIDs for background ANRs unless
+                        // it is the process of interest
+                        String[] nativeProcs = null;
+                        if (isSilentAnr || onlyDumpSelf) {
+                            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
+                                if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
+                                    nativeProcs = new String[] { mApp.processName };
+                                    break;
+                                }
+                            }
+                        } else {
+                            nativeProcs = NATIVE_STACKS_OF_INTEREST;
+                        }
 
-        int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
-        ArrayList<Integer> nativePids = null;
+                        int[] pids = nativeProcs == null
+                                ? null : Process.getPidsForCommands(nativeProcs);
+                        ArrayList<Integer> nativePids = null;
 
-        if (pids != null) {
-            nativePids = new ArrayList<>(pids.length);
-            for (int i : pids) {
-                nativePids.add(i);
-            }
-        }
-        latencyTracker.nativePidCollectionEnded();
+                        if (pids != null) {
+                            nativePids = new ArrayList<>(pids.length);
+                            for (int i : pids) {
+                                nativePids.add(i);
+                            }
+                        }
+                        latencyTracker.nativePidCollectionEnded();
+                        return nativePids;
+                    });
+
         // For background ANRs, don't pass the ProcessCpuTracker to
         // avoid spending 1/2 second collecting stats to rank lastPids.
         StringWriter tracesFileException = new StringWriter();
@@ -463,10 +483,18 @@
         final AtomicLong firstPidEndOffset = new AtomicLong(-1);
         File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
                 isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
-                nativePids, tracesFileException, firstPidEndOffset, annotation, criticalEventLog,
-                latencyTracker);
+                nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
+                criticalEventLog, auxiliaryTaskExecutor, latencyTracker);
 
         if (isMonitorCpuUsage()) {
+            // Wait for the first call to finish
+            try {
+                updateCpuStatsNowFirstCall.get();
+            } catch (ExecutionException e) {
+                Slog.w(TAG, "Failed to update the CPU stats", e.getCause());
+            } catch (InterruptedException e) {
+                Slog.w(TAG, "Interrupted while updating the CPU stats", e);
+            }
             mService.updateCpuStatsNow();
             mService.mAppProfiler.printCurrentCpuState(report, anrTime);
             info.append(processCpuTracker.printCurrentLoad());
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8d3890c..af55980 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2190,7 +2190,7 @@
         if (oldUserId == UserHandle.USER_SYSTEM) {
             // System user is never stopped, but its visibility is changed (as it is brought to the
             // background)
-            updateSystemUserVisibility(/* visible= */ false);
+            updateSystemUserVisibility(t, /* visible= */ false);
         }
 
         t.traceEnd(); // end continueUserSwitch
@@ -2549,10 +2549,15 @@
 
     // TODO(b/242195409): remove this method if initial system user boot logic is refactored?
     void onSystemUserStarting() {
-        updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode());
+        if (!UserManager.isHeadlessSystemUserMode()) {
+            // Don't need to call on HSUM because it will be called when the system user is
+            // restarted on background
+            mInjector.onUserStarting(UserHandle.USER_SYSTEM, /* visible= */ true);
+        }
     }
 
-    private void updateSystemUserVisibility(boolean visible) {
+    private void updateSystemUserVisibility(TimingsTraceAndSlog t, boolean visible) {
+        t.traceBegin("update-system-userVisibility-" + visible);
         if (DEBUG_MU) {
             Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
         }
@@ -2564,7 +2569,8 @@
                 mVisibleUsers.delete(userId);
             }
         }
-        mInjector.onUserStarting(userId, visible);
+        mInjector.notifySystemUserVisibilityChanged(visible);
+        t.traceEnd();
     }
 
     /**
@@ -3673,5 +3679,8 @@
             getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
                     visible);
         }
+        void notifySystemUserVisibilityChanged(boolean visible) {
+            getSystemServiceManager().onSystemUserVisibilityChanged(visible);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 4aaf1ab..908cb3f 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -185,6 +185,8 @@
                 @Override
                 @EnforcePermission(MANAGE_GAME_ACTIVITY)
                 public void createGameSession(int taskId) {
+                    super.createGameSession_enforcePermission();
+
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.createGameSession(taskId);
                     });
@@ -197,6 +199,8 @@
                 @EnforcePermission(MANAGE_GAME_ACTIVITY)
                 public void takeScreenshot(int taskId,
                         @NonNull AndroidFuture gameScreenshotResultFuture) {
+                    super.takeScreenshot_enforcePermission();
+
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.takeScreenshot(taskId,
                                 gameScreenshotResultFuture);
@@ -206,6 +210,8 @@
                 @Override
                 @EnforcePermission(MANAGE_GAME_ACTIVITY)
                 public void restartGame(int taskId) {
+                    super.restartGame_enforcePermission();
+
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.restartGame(taskId);
                     });
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index bbffc89..2fe06094 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -286,22 +286,9 @@
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
         }
-
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                AudioDeviceAttributes device = null;
-                if (on) {
-                    device = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
-                } else {
-                    CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
-                    if (client == null || !client.requestsSpeakerphone()) {
-                        return;
-                    }
-                }
-                postSetCommunicationRouteForClient(new CommunicationClientInfo(
-                        cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource));
-            }
-        }
+        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
     }
 
     /**
@@ -311,6 +298,9 @@
      * @param device Device selected or null to unselect.
      * @param eventSource for logging purposes
      */
+
+    private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
+
     /*package*/ boolean setCommunicationDevice(
             IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
 
@@ -318,21 +308,53 @@
             Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
         }
 
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                AudioDeviceAttributes deviceAttr = null;
-                if (device != null) {
-                    deviceAttr = new AudioDeviceAttributes(device);
-                } else {
-                    CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
-                    if (client == null) {
-                        return false;
+        AudioDeviceAttributes deviceAttr =
+                (device != null) ? new AudioDeviceAttributes(device) : null;
+        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
+                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
+        postSetCommunicationDeviceForClient(deviceInfo);
+        boolean status;
+        synchronized (deviceInfo) {
+            final long start = System.currentTimeMillis();
+            long elapsed = 0;
+            while (deviceInfo.mWaitForStatus) {
+                try {
+                    deviceInfo.wait(SET_COMMUNICATION_DEVICE_TIMEOUT_MS - elapsed);
+                } catch (InterruptedException e) {
+                    elapsed = System.currentTimeMillis() - start;
+                    if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) {
+                        deviceInfo.mStatus = false;
+                        deviceInfo.mWaitForStatus = false;
                     }
                 }
-                postSetCommunicationRouteForClient(new CommunicationClientInfo(
-                        cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource));
+            }
+            status = deviceInfo.mStatus;
+        }
+        return status;
+    }
+
+    /**
+     * Sets or resets the communication device for matching client. If no client matches and the
+     * request is to reset for a given device (deviceInfo.mOn == false), the method is a noop.
+     * @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo}
+     * @return true if the communication device is set or reset
+     */
+    @GuardedBy("mDeviceStateLock")
+    /*package*/ boolean onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
+        }
+        if (!deviceInfo.mOn) {
+            CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
+            if (client == null || (deviceInfo.mDevice != null
+                    && !deviceInfo.mDevice.equals(client.getDevice()))) {
+                return false;
             }
         }
+
+        AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
+        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
+                deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
         return true;
     }
 
@@ -390,7 +412,7 @@
             mBtHelper.stopBluetoothSco(eventSource);
         }
 
-        sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
+        updateCommunicationRoute(eventSource);
     }
 
     /**
@@ -424,7 +446,7 @@
         CommunicationRouteClient crc = topCommunicationRouteClient();
         AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "requestedCommunicationDevice, device: "
+            Log.v(TAG, "requestedCommunicationDevice: "
                     + device + " mAudioModeOwner: " + mAudioModeOwner.toString());
         }
         return device;
@@ -822,37 +844,22 @@
                 @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "startBluetoothScoForClient_Sync, pid: " + pid);
+            Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
         }
-
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                AudioDeviceAttributes device =
-                        new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
-
-                postSetCommunicationRouteForClient(new CommunicationClientInfo(
-                        cb, pid, device, scoAudioMode, eventSource));
-            }
-        }
+        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                true, scoAudioMode, eventSource, false));
     }
 
     /*package*/ void stopBluetoothScoForClient(
                         IBinder cb, int pid, @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "stopBluetoothScoForClient_Sync, pid: " + pid);
+            Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
         }
-
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
-                if (client == null || !client.requestsBluetoothSco()) {
-                    return;
-                }
-                postSetCommunicationRouteForClient(new CommunicationClientInfo(
-                        cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource));
-            }
-        }
+        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
     }
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -990,7 +997,8 @@
     }
 
     //---------------------------------------------------------------------
-    // Message handling on behalf of helper classes
+    // Message handling on behalf of helper classes.
+    // Each of these methods posts a message to mBrokerHandler message queue.
     /*package*/ void postBroadcastScoConnectionState(int state) {
         sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
     }
@@ -1046,28 +1054,34 @@
         sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource);
     }
 
-    /*package*/ void postSetCommunicationRouteForClient(CommunicationClientInfo info) {
-        sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT, SENDMSG_QUEUE, info);
+    /*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
+        sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT, SENDMSG_QUEUE, info);
     }
 
     /*package*/ void postScoAudioStateChanged(int state) {
         sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
     }
 
-    /*package*/ static final class CommunicationClientInfo {
-        final @NonNull IBinder mCb;
-        final int mPid;
-        final @NonNull AudioDeviceAttributes mDevice;
-        final int mScoAudioMode;
-        final @NonNull String mEventSource;
+    /*package*/ static final class CommunicationDeviceInfo {
+        final @NonNull IBinder mCb; // Identifies the requesting client for death handler
+        final int mPid; // Requester process ID
+        final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
+        final boolean mOn; // true if setting, false if resetting
+        final int mScoAudioMode; // only used for SCO: requested audio mode
+        final @NonNull String mEventSource; // caller identifier for logging
+        boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
+        boolean mStatus = false; // completion status only used if mWaitForStatus is true
 
-        CommunicationClientInfo(@NonNull IBinder cb, int pid, @NonNull AudioDeviceAttributes device,
-                int scoAudioMode, @NonNull String eventSource) {
+        CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
+                @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
+                @NonNull String eventSource, boolean waitForStatus) {
             mCb = cb;
             mPid = pid;
             mDevice = device;
+            mOn = on;
             mScoAudioMode = scoAudioMode;
             mEventSource = eventSource;
+            mWaitForStatus = waitForStatus;
         }
 
         // redefine equality op so we can match messages intended for this client
@@ -1079,21 +1093,24 @@
             if (this == o) {
                 return true;
             }
-            if (!(o instanceof CommunicationClientInfo)) {
+            if (!(o instanceof CommunicationDeviceInfo)) {
                 return false;
             }
 
-            return mCb.equals(((CommunicationClientInfo) o).mCb)
-                    && mPid == ((CommunicationClientInfo) o).mPid;
+            return mCb.equals(((CommunicationDeviceInfo) o).mCb)
+                    && mPid == ((CommunicationDeviceInfo) o).mPid;
         }
 
         @Override
         public String toString() {
-            return "CommunicationClientInfo mCb=" + mCb.toString()
-                    +"mPid=" + mPid
-                    +"mDevice=" + mDevice.toString()
-                    +"mScoAudioMode=" + mScoAudioMode
-                    +"mEventSource=" + mEventSource;
+            return "CommunicationDeviceInfo mCb=" + mCb.toString()
+                    + " mPid=" + mPid
+                    + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
+                    + " mOn=" + mOn
+                    + " mScoAudioMode=" + mScoAudioMode
+                    + " mEventSource=" + mEventSource
+                    + " mWaitForStatus=" + mWaitForStatus
+                    + " mStatus=" + mStatus;
         }
     }
 
@@ -1297,7 +1314,7 @@
                             updateActiveCommunicationDevice();
                             mDeviceInventory.onRestoreDevices();
                             mBtHelper.onAudioServerDiedRestoreA2dp();
-                            onUpdateCommunicationRoute("MSG_RESTORE_DEVICES");
+                            updateCommunicationRoute("MSG_RESTORE_DEVICES");
                         }
                     }
                     break;
@@ -1392,12 +1409,19 @@
                     }
                     break;
 
-                case MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT:
+                case MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT:
+                    CommunicationDeviceInfo deviceInfo = (CommunicationDeviceInfo) msg.obj;
+                    boolean status;
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            CommunicationClientInfo info = (CommunicationClientInfo) msg.obj;
-                            setCommunicationRouteForClient(info.mCb, info.mPid, info.mDevice,
-                                    info.mScoAudioMode, info.mEventSource);
+                            status = onSetCommunicationDeviceForClient(deviceInfo);
+                        }
+                    }
+                    synchronized (deviceInfo) {
+                        if (deviceInfo.mWaitForStatus) {
+                            deviceInfo.mStatus = status;
+                            deviceInfo.mWaitForStatus = false;
+                            deviceInfo.notify();
                         }
                     }
                     break;
@@ -1410,14 +1434,6 @@
                     }
                     break;
 
-                case MSG_L_UPDATE_COMMUNICATION_ROUTE:
-                    synchronized (mSetModeLock) {
-                        synchronized (mDeviceStateLock) {
-                            onUpdateCommunicationRoute((String) msg.obj);
-                        }
-                    }
-                    break;
-
                 case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
@@ -1568,8 +1584,7 @@
     private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 37;
     private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
 
-    private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39;
-    private static final int MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT = 42;
+    private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
     private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
     private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
 
@@ -1793,18 +1808,6 @@
         AudioDeviceAttributes getDevice() {
             return mDevice;
         }
-
-        boolean requestsBluetoothSco() {
-            return mDevice != null
-                    && mDevice.getType()
-                        == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
-        }
-
-        boolean requestsSpeakerphone() {
-            return mDevice != null
-                    && mDevice.getType()
-                        == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
-        }
     }
 
     // @GuardedBy("mSetModeLock")
@@ -1852,14 +1855,14 @@
      */
     // @GuardedBy("mSetModeLock")
     @GuardedBy("mDeviceStateLock")
-    private void onUpdateCommunicationRoute(String eventSource) {
+    private void updateCommunicationRoute(String eventSource) {
         AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice();
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+            Log.v(TAG, "updateCommunicationRoute, preferredCommunicationDevice: "
                     + preferredCommunicationDevice + " eventSource: " + eventSource);
         }
         AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+                "updateCommunicationRoute, preferredCommunicationDevice: "
                 + preferredCommunicationDevice + " eventSource: " + eventSource)));
 
         if (preferredCommunicationDevice == null
@@ -1895,7 +1898,7 @@
     // @GuardedBy("mSetModeLock")
     @GuardedBy("mDeviceStateLock")
     private void onUpdateCommunicationRouteClient(String eventSource) {
-        onUpdateCommunicationRoute(eventSource);
+        updateCommunicationRoute(eventSource);
         CommunicationRouteClient crc = topCommunicationRouteClient();
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "onUpdateCommunicationRouteClient, crc: "
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9d6fa9e..f8a6462 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1859,6 +1859,8 @@
      * @see AudioManager#setSupportedSystemUsages(int[])
      */
     public void setSupportedSystemUsages(@NonNull @AttributeSystemUsage int[] systemUsages) {
+        super.setSupportedSystemUsages_enforcePermission();
+
         verifySystemUsages(systemUsages);
 
         synchronized (mSupportedSystemUsagesLock) {
@@ -1872,6 +1874,8 @@
      * @see AudioManager#getSupportedSystemUsages()
      */
     public @NonNull @AttributeSystemUsage int[] getSupportedSystemUsages() {
+        super.getSupportedSystemUsages_enforcePermission();
+
         synchronized (mSupportedSystemUsagesLock) {
             return Arrays.copyOf(mSupportedSystemUsages, mSupportedSystemUsages.length);
         }
@@ -1893,6 +1897,8 @@
     @NonNull
     public List<AudioProductStrategy> getAudioProductStrategies() {
         // verify permissions
+        super.getAudioProductStrategies_enforcePermission();
+
         return AudioProductStrategy.getAudioProductStrategies();
     }
 
@@ -1904,6 +1910,8 @@
     @NonNull
     public List<AudioVolumeGroup> getAudioVolumeGroups() {
         // verify permissions
+        super.getAudioVolumeGroups_enforcePermission();
+
         return AudioVolumeGroup.getAudioVolumeGroups();
     }
 
@@ -2782,6 +2790,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */
     public int removePreferredDevicesForStrategy(int strategy) {
+        super.removePreferredDevicesForStrategy_enforcePermission();
+
         final String logString =
                 String.format("removePreferredDeviceForStrategy strat:%d", strategy);
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2799,6 +2809,8 @@
      * @see AudioManager#getPreferredDevicesForStrategy(AudioProductStrategy)
      */
     public List<AudioDeviceAttributes> getPreferredDevicesForStrategy(int strategy) {
+        super.getPreferredDevicesForStrategy_enforcePermission();
+
         List<AudioDeviceAttributes> devices = new ArrayList<>();
         final long identity = Binder.clearCallingIdentity();
         final int status = AudioSystem.getDevicesForRoleAndStrategy(
@@ -2869,6 +2881,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#clearPreferredDevicesForCapturePreset(int) */
     public int clearPreferredDevicesForCapturePreset(int capturePreset) {
+        super.clearPreferredDevicesForCapturePreset_enforcePermission();
+
         final String logString = String.format(
                 "removePreferredDeviceForCapturePreset source:%d", capturePreset);
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2885,6 +2899,8 @@
      * @see AudioManager#getPreferredDevicesForCapturePreset(int)
      */
     public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) {
+        super.getPreferredDevicesForCapturePreset_enforcePermission();
+
         List<AudioDeviceAttributes> devices = new ArrayList<>();
         final long identity = Binder.clearCallingIdentity();
         final int status = AudioSystem.getDevicesForRoleAndCapturePreset(
@@ -3618,6 +3634,8 @@
     /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */
     public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,
             String callingPackage, String attributionTag) {
+        super.setVolumeIndexForAttributes_enforcePermission();
+
         Objects.requireNonNull(attr, "attr must not be null");
         final int volumeGroup = getVolumeGroupIdForAttributes(attr);
         if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
@@ -3661,6 +3679,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#getVolumeIndexForAttributes(attr) */
     public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+        super.getVolumeIndexForAttributes_enforcePermission();
+
         Objects.requireNonNull(attr, "attr must not be null");
         final int volumeGroup = getVolumeGroupIdForAttributes(attr);
         if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
@@ -3673,6 +3693,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
     public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+        super.getMaxVolumeIndexForAttributes_enforcePermission();
+
         Objects.requireNonNull(attr, "attr must not be null");
         return AudioSystem.getMaxVolumeIndexForAttributes(attr);
     }
@@ -3680,6 +3702,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */
     public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+        super.getMinVolumeIndexForAttributes_enforcePermission();
+
         Objects.requireNonNull(attr, "attr must not be null");
         return AudioSystem.getMinVolumeIndexForAttributes(attr);
     }
@@ -3786,6 +3810,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND)
     /** @see AudioManager#isUltrasoundSupported() */
     public boolean isUltrasoundSupported() {
+        super.isUltrasoundSupported_enforcePermission();
+
         return AudioSystem.isUltrasoundSupported();
     }
 
@@ -4603,6 +4629,8 @@
     /** @see AudioManager#setMasterMute(boolean, int) */
     public void setMasterMute(boolean mute, int flags, String callingPackage, int userId,
             String attributionTag) {
+        super.setMasterMute_enforcePermission();
+
         setMasterMuteInternal(mute, flags, callingPackage,
                 Binder.getCallingUid(), userId, Binder.getCallingPid(), attributionTag);
     }
@@ -4647,6 +4675,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE)
     /** Get last audible volume before stream was muted. */
     public int getLastAudibleStreamVolume(int streamType) {
+        super.getLastAudibleStreamVolume_enforcePermission();
+
         ensureValidStreamType(streamType);
         int device = getDeviceForStream(streamType);
         return (mStreamStates[streamType].getIndex(device) + 5) / 10;
@@ -5500,6 +5530,8 @@
     /** @see AudioManager#isPstnCallAudioInterceptable() */
     public boolean isPstnCallAudioInterceptable() {
 
+        super.isPstnCallAudioInterceptable_enforcePermission();
+
         boolean uplinkDeviceFound = false;
         boolean downlinkDeviceFound = false;
         AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_ALL);
@@ -6891,6 +6923,8 @@
             @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) {
         // verify permissions
         // verify arguments
+        super.setDeviceVolumeBehavior_enforcePermission();
+
         Objects.requireNonNull(device);
         AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
         sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
@@ -7046,6 +7080,8 @@
      */
     public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
             @ConnectionState int state, String caller) {
+        super.setWiredDeviceConnectionState_enforcePermission();
+
         if (state != CONNECTION_STATE_CONNECTED
                 && state != CONNECTION_STATE_DISCONNECTED) {
             throw new IllegalArgumentException("Invalid state " + state);
@@ -9178,24 +9214,32 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#isAvailableForDevice(AudioDeviceAttributes) */
     public boolean isSpatializerAvailableForDevice(@NonNull AudioDeviceAttributes device)  {
+        super.isSpatializerAvailableForDevice_enforcePermission();
+
         return mSpatializerHelper.isAvailableForDevice(Objects.requireNonNull(device));
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#hasHeadTracker(AudioDeviceAttributes) */
     public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) {
+        super.hasHeadTracker_enforcePermission();
+
         return mSpatializerHelper.hasHeadTracker(Objects.requireNonNull(device));
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setHeadTrackerEnabled(boolean, AudioDeviceAttributes) */
     public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) {
+        super.setHeadTrackerEnabled_enforcePermission();
+
         mSpatializerHelper.setHeadTrackerEnabled(enabled, Objects.requireNonNull(device));
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#isHeadTrackerEnabled(AudioDeviceAttributes) */
     public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) {
+        super.isHeadTrackerEnabled_enforcePermission();
+
         return mSpatializerHelper.isHeadTrackerEnabled(Objects.requireNonNull(device));
     }
 
@@ -9207,6 +9251,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setSpatializerEnabled(boolean) */
     public void setSpatializerEnabled(boolean enabled) {
+        super.setSpatializerEnabled_enforcePermission();
+
         mSpatializerHelper.setFeatureEnabled(enabled);
     }
 
@@ -9236,6 +9282,8 @@
     /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
     public void registerSpatializerHeadTrackingCallback(
             @NonNull ISpatializerHeadTrackingModeCallback cb) {
+        super.registerSpatializerHeadTrackingCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.registerHeadTrackingModeCallback(cb);
     }
@@ -9244,6 +9292,8 @@
     /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
     public void unregisterSpatializerHeadTrackingCallback(
             @NonNull ISpatializerHeadTrackingModeCallback cb) {
+        super.unregisterSpatializerHeadTrackingCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.unregisterHeadTrackingModeCallback(cb);
     }
@@ -9259,6 +9309,8 @@
     /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */
     public void registerHeadToSoundstagePoseCallback(
             @NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+        super.registerHeadToSoundstagePoseCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb);
     }
@@ -9267,6 +9319,8 @@
     /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */
     public void unregisterHeadToSoundstagePoseCallback(
             @NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+        super.unregisterHeadToSoundstagePoseCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb);
     }
@@ -9274,12 +9328,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getSpatializerCompatibleAudioDevices() */
     public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() {
+        super.getSpatializerCompatibleAudioDevices_enforcePermission();
+
         return mSpatializerHelper.getCompatibleAudioDevices();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#addSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */
     public void addSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+        super.addSpatializerCompatibleAudioDevice_enforcePermission();
+
         Objects.requireNonNull(ada);
         mSpatializerHelper.addCompatibleAudioDevice(ada);
     }
@@ -9287,6 +9345,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#removeSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */
     public void removeSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+        super.removeSpatializerCompatibleAudioDevice_enforcePermission();
+
         Objects.requireNonNull(ada);
         mSpatializerHelper.removeCompatibleAudioDevice(ada);
     }
@@ -9294,24 +9354,32 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getSupportedHeadTrackingModes() */
     public int[] getSupportedHeadTrackingModes() {
+        super.getSupportedHeadTrackingModes_enforcePermission();
+
         return mSpatializerHelper.getSupportedHeadTrackingModes();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getHeadTrackingMode() */
     public int getActualHeadTrackingMode() {
+        super.getActualHeadTrackingMode_enforcePermission();
+
         return mSpatializerHelper.getActualHeadTrackingMode();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getDesiredHeadTrackingMode() */
     public int getDesiredHeadTrackingMode() {
+        super.getDesiredHeadTrackingMode_enforcePermission();
+
         return mSpatializerHelper.getDesiredHeadTrackingMode();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setGlobalTransform */
     public void setSpatializerGlobalTransform(@NonNull float[] transform) {
+        super.setSpatializerGlobalTransform_enforcePermission();
+
         Objects.requireNonNull(transform);
         mSpatializerHelper.setGlobalTransform(transform);
     }
@@ -9319,12 +9387,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#recenterHeadTracker() */
     public void recenterHeadTracker() {
+        super.recenterHeadTracker_enforcePermission();
+
         mSpatializerHelper.recenterHeadTracker();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setDesiredHeadTrackingMode */
     public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
+        super.setDesiredHeadTrackingMode_enforcePermission();
+
         switch(mode) {
             case Spatializer.HEAD_TRACKING_MODE_DISABLED:
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
@@ -9339,6 +9411,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setEffectParameter */
     public void setSpatializerParameter(int key, @NonNull byte[] value) {
+        super.setSpatializerParameter_enforcePermission();
+
         Objects.requireNonNull(value);
         mSpatializerHelper.setEffectParameter(key, value);
     }
@@ -9346,6 +9420,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getEffectParameter */
     public void getSpatializerParameter(int key, @NonNull byte[] value) {
+        super.getSpatializerParameter_enforcePermission();
+
         Objects.requireNonNull(value);
         mSpatializerHelper.getEffectParameter(key, value);
     }
@@ -9353,12 +9429,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getOutput */
     public int getSpatializerOutput() {
+        super.getSpatializerOutput_enforcePermission();
+
         return mSpatializerHelper.getOutput();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setOnSpatializerOutputChangedListener */
     public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+        super.registerSpatializerOutputCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.registerSpatializerOutputCallback(cb);
     }
@@ -9366,6 +9446,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#clearOnSpatializerOutputChangedListener */
     public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+        super.unregisterSpatializerOutputCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.unregisterSpatializerOutputCallback(cb);
     }
@@ -9483,6 +9565,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#getMutingExpectedDevice */
     public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+        super.getMutingExpectedDevice_enforcePermission();
+
         synchronized (mMuteAwaitConnectionLock) {
             return mMutingExpectedDevice;
         }
@@ -9524,6 +9608,8 @@
     /** @see AudioManager#registerMuteAwaitConnectionCallback */
     public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb,
             boolean register) {
+        super.registerMuteAwaitConnectionDispatcher_enforcePermission();
+
         if (register) {
             mMuteAwaitConnectionDispatchers.register(cb);
         } else {
@@ -11049,6 +11135,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioPolicy#getFocusStack() */
     public List<AudioFocusInfo> getFocusStack() {
+        super.getFocusStack_enforcePermission();
+
         return mMediaFocusControl.getFocusStack();
     }
 
@@ -11826,6 +11914,8 @@
     // Multi Audio Focus
     //======================
     public void setMultiAudioFocusEnabled(boolean enabled) {
+        super.setMultiAudioFocusEnabled_enforcePermission();
+
         if (mMediaFocusControl != null) {
             boolean mafEnabled = mMediaFocusControl.getMultiAudioFocusEnabled();
             if (mafEnabled != enabled) {
@@ -11928,6 +12018,8 @@
     /** @see AudioManager#addAssistantServicesUids(int []) */
     @Override
     public void addAssistantServicesUids(int [] assistantUids) {
+        super.addAssistantServicesUids_enforcePermission();
+
         Objects.requireNonNull(assistantUids);
 
         synchronized (mSettingsLock) {
@@ -11939,6 +12031,8 @@
     /** @see AudioManager#removeAssistantServicesUids(int []) */
     @Override
     public void removeAssistantServicesUids(int [] assistantUids) {
+        super.removeAssistantServicesUids_enforcePermission();
+
         Objects.requireNonNull(assistantUids);
         synchronized (mSettingsLock) {
             removeAssistantServiceUidsLocked(assistantUids);
@@ -11949,6 +12043,8 @@
     /** @see AudioManager#getAssistantServicesUids() */
     @Override
     public int[] getAssistantServicesUids() {
+        super.getAssistantServicesUids_enforcePermission();
+
         int [] assistantUids;
         synchronized (mSettingsLock) {
             assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray();
@@ -11960,6 +12056,8 @@
     /** @see AudioManager#setActiveAssistantServiceUids(int []) */
     @Override
     public void setActiveAssistantServiceUids(int [] activeAssistantUids) {
+        super.setActiveAssistantServiceUids_enforcePermission();
+
         Objects.requireNonNull(activeAssistantUids);
         synchronized (mSettingsLock) {
             mActiveAssistantServiceUids = activeAssistantUids;
@@ -11971,6 +12069,8 @@
     /** @see AudioManager#getActiveAssistantServiceUids() */
     @Override
     public int[] getActiveAssistantServiceUids() {
+        super.getActiveAssistantServiceUids_enforcePermission();
+
         int [] activeAssistantUids;
         synchronized (mSettingsLock) {
             activeAssistantUids = mActiveAssistantServiceUids.clone();
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 74bfa80..0bc4b20 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,12 +17,12 @@
 package com.android.server.audio;
 
 import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_CLIENT_VOLUME;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_MASTER;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_PLAYBACK_RESTRICTED;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_MUTED;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_VOLUME;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_VOLUME_SHAPER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
 import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
 import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
 
@@ -1155,22 +1155,22 @@
                     if (mEventValue <= 0) {
                         builder.append("none ");
                     } else {
-                        if ((mEventValue & PLAYER_MUTE_MASTER) != 0) {
+                        if ((mEventValue & MUTED_BY_MASTER) != 0) {
                             builder.append("masterMute ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_STREAM_VOLUME) != 0) {
+                        if ((mEventValue & MUTED_BY_STREAM_VOLUME) != 0) {
                             builder.append("streamVolume ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_STREAM_MUTED) != 0) {
+                        if ((mEventValue & MUTED_BY_STREAM_MUTED) != 0) {
                             builder.append("streamMute ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) {
-                            builder.append("playbackRestricted ");
+                        if ((mEventValue & MUTED_BY_APP_OPS) != 0) {
+                            builder.append("appOps ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_CLIENT_VOLUME) != 0) {
+                        if ((mEventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
                             builder.append("clientVolume ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_VOLUME_SHAPER) != 0) {
+                        if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
                             builder.append("volumeShaper ");
                         }
                     }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d2016c47..4358ee2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -178,6 +178,8 @@
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) throws RemoteException {
 
+            super.createTestSession_enforcePermission();
+
             final long identity = Binder.clearCallingIdentity();
             try {
                 return mInjector.getBiometricService()
@@ -192,6 +194,8 @@
         public List<SensorPropertiesInternal> getSensorProperties(String opPackageName)
                 throws RemoteException {
 
+            super.getSensorProperties_enforcePermission();
+
             final long identity = Binder.clearCallingIdentity();
             try {
                 // Get the result from BiometricService, since it is the source of truth for all
@@ -206,6 +210,8 @@
         @Override
         public String getUiPackage() {
 
+            super.getUiPackage_enforcePermission();
+
             return getContext().getResources()
                     .getString(R.string.config_biometric_prompt_ui_package);
         }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index c29755a..cd30d26 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -495,6 +495,8 @@
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) throws RemoteException {
 
+            super.createTestSession_enforcePermission();
+
             for (BiometricSensor sensor : mSensors) {
                 if (sensor.id == sensorId) {
                     return sensor.impl.createTestSession(callback, opPackageName);
@@ -510,6 +512,8 @@
         public List<SensorPropertiesInternal> getSensorProperties(String opPackageName)
                 throws RemoteException {
 
+            super.getSensorProperties_enforcePermission();
+
             final List<SensorPropertiesInternal> sensors = new ArrayList<>();
             for (BiometricSensor sensor : mSensors) {
                 // Explicitly re-create as the super class, since AIDL doesn't play nicely with
@@ -526,6 +530,8 @@
         @Override // Binder call
         public void onReadyForAuthentication(long requestId, int cookie) {
 
+            super.onReadyForAuthentication_enforcePermission();
+
             mHandler.post(() -> handleOnReadyForAuthentication(requestId, cookie));
         }
 
@@ -534,6 +540,8 @@
         public long authenticate(IBinder token, long operationId, int userId,
                 IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
 
+            super.authenticate_enforcePermission();
+
             if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
                 Slog.e(TAG, "Unable to authenticate, one or more null arguments");
                 return -1;
@@ -564,6 +572,8 @@
         @Override // Binder call
         public void cancelAuthentication(IBinder token, String opPackageName, long requestId) {
 
+            super.cancelAuthentication_enforcePermission();
+
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = token;
             args.arg2 = opPackageName;
@@ -577,6 +587,8 @@
         public int canAuthenticate(String opPackageName, int userId, int callingUserId,
                 @Authenticators.Types int authenticators) {
 
+            super.canAuthenticate_enforcePermission();
+
             Slog.d(TAG, "canAuthenticate: User=" + userId
                     + ", Caller=" + callingUserId
                     + ", Authenticators=" + authenticators);
@@ -599,6 +611,8 @@
         @Override
         public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
 
+            super.hasEnrolledBiometrics_enforcePermission();
+
             try {
                 for (BiometricSensor sensor : mSensors) {
                     if (sensor.impl.hasEnrolledTemplates(userId, opPackageName)) {
@@ -618,6 +632,8 @@
                 @Authenticators.Types int strength,
                 @NonNull IBiometricAuthenticator authenticator) {
 
+            super.registerAuthenticator_enforcePermission();
+
             Slog.d(TAG, "Registering ID: " + id
                     + " Modality: " + modality
                     + " Strength: " + strength);
@@ -664,6 +680,8 @@
         public void registerEnabledOnKeyguardCallback(
                 IBiometricEnabledOnKeyguardCallback callback, int callingUserId) {
 
+            super.registerEnabledOnKeyguardCallback_enforcePermission();
+
             mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback));
             try {
                 callback.onChanged(mSettingObserver.getEnabledOnKeyguard(callingUserId),
@@ -678,6 +696,8 @@
         public void invalidateAuthenticatorIds(int userId, int fromSensorId,
                 IInvalidationCallback callback) {
 
+            super.invalidateAuthenticatorIds_enforcePermission();
+
             InvalidationTracker.start(getContext(), mSensors, userId, fromSensorId, callback);
         }
 
@@ -685,6 +705,8 @@
         @Override // Binder call
         public long[] getAuthenticatorIds(int callingUserId) {
 
+            super.getAuthenticatorIds_enforcePermission();
+
             final List<Long> authenticatorIds = new ArrayList<>();
             for (BiometricSensor sensor : mSensors) {
                 try {
@@ -717,6 +739,8 @@
                 int userId, byte[] hardwareAuthToken) {
 
             // Check originating strength
+            super.resetLockoutTimeBound_enforcePermission();
+
             if (!Utils.isAtLeastStrength(getSensorForId(fromSensorId).getCurrentStrength(),
                     Authenticators.BIOMETRIC_STRONG)) {
                 Slog.w(TAG, "Sensor: " + fromSensorId + " is does not meet the required strength to"
@@ -754,6 +778,8 @@
         @Override // Binder call
         public int getCurrentStrength(int sensorId) {
 
+            super.getCurrentStrength_enforcePermission();
+
             for (BiometricSensor sensor : mSensors) {
                 if (sensor.id == sensorId) {
                     return sensor.getCurrentStrength();
@@ -772,6 +798,8 @@
                 @Authenticators.Types int authenticators) {
 
 
+            super.getCurrentModality_enforcePermission();
+
             Slog.d(TAG, "getCurrentModality: User=" + userId
                     + ", Caller=" + callingUserId
                     + ", Authenticators=" + authenticators);
@@ -794,6 +822,8 @@
         @Override // Binder call
         public int getSupportedModalities(@Authenticators.Types int authenticators) {
 
+            super.getSupportedModalities_enforcePermission();
+
             Slog.d(TAG, "getSupportedModalities: Authenticators=" + authenticators);
 
             if (!Utils.isValidAuthenticatorConfig(authenticators)) {
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 7a5b584..33d3b64 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
@@ -99,6 +99,8 @@
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
+            super.createTestSession_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
 
             if (provider == null) {
@@ -112,6 +114,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
+            super.dumpSensorServiceStateProto_enforcePermission();
+
             final ProtoOutputStream proto = new ProtoOutputStream();
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider != null) {
@@ -125,6 +129,8 @@
         @Override // Binder call
         public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
                 String opPackageName) {
+            super.getSensorPropertiesInternal_enforcePermission();
+
             return mRegistry.getAllProperties();
         }
 
@@ -132,6 +138,8 @@
         @Override // Binder call
         public FaceSensorPropertiesInternal getSensorProperties(int sensorId,
                 @NonNull String opPackageName) {
+            super.getSensorProperties_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
@@ -146,6 +154,8 @@
         @Override // Binder call
         public void generateChallenge(IBinder token, int sensorId, int userId,
                 IFaceServiceReceiver receiver, String opPackageName) {
+            super.generateChallenge_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
@@ -159,6 +169,8 @@
         @Override // Binder call
         public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                 long challenge) {
+            super.revokeChallenge_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
@@ -173,6 +185,8 @@
         public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
+            super.enroll_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
@@ -201,12 +215,16 @@
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures) {
             // TODO(b/145027036): Implement this.
+            super.enrollRemotely_enforcePermission();
+
             return -1;
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
         public void cancelEnrollment(final IBinder token, long requestId) {
+            super.cancelEnrollment_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelEnrollment");
@@ -224,6 +242,8 @@
             // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
             //  lockdown, something wrong happened. See similar path in FingerprintService.
 
+            super.authenticate_enforcePermission();
+
             final boolean restricted = false; // Face APIs are private
             final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
                     ? BiometricsProtoEnums.CLIENT_KEYGUARD
@@ -249,6 +269,8 @@
         @Override // Binder call
         public long detectFace(final IBinder token, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
+            super.detectFace_enforcePermission();
+
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "detectFace called from non-sysui package: " + opPackageName);
                 return -1;
@@ -278,6 +300,8 @@
                 IBinder token, long operationId, int userId,
                 IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
                 int cookie, boolean allowBackgroundAuthentication) {
+            super.prepareForAuthentication_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
@@ -295,6 +319,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public void startPreparedClient(int sensorId, int cookie) {
+            super.startPreparedClient_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for startPreparedClient");
@@ -308,6 +334,8 @@
         @Override // Binder call
         public void cancelAuthentication(final IBinder token, final String opPackageName,
                 final long requestId) {
+            super.cancelAuthentication_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthentication");
@@ -321,6 +349,8 @@
         @Override // Binder call
         public void cancelFaceDetect(final IBinder token, final String opPackageName,
                 final long requestId) {
+            super.cancelFaceDetect_enforcePermission();
+
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "cancelFaceDetect called from non-sysui package: "
                         + opPackageName);
@@ -340,6 +370,8 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(int sensorId, final IBinder token,
                 final String opPackageName, final long requestId) {
+            super.cancelAuthenticationFromService_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
@@ -353,6 +385,8 @@
         @Override // Binder call
         public void remove(final IBinder token, final int faceId, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
+            super.remove_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for remove");
@@ -367,6 +401,8 @@
         @Override // Binder call
         public void removeAll(final IBinder token, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
+            super.removeAll_enforcePermission();
+
             final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() {
                 int sensorsFinishedRemoving = 0;
                 final int numSensors = getSensorPropertiesInternal(
@@ -399,6 +435,8 @@
         @Override // Binder call
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
+            super.addLockoutResetCallback_enforcePermission();
+
             mLockoutResetDispatcher.addCallback(callback, opPackageName);
         }
 
@@ -458,6 +496,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean isHardwareDetected(int sensorId, String opPackageName) {
+            super.isHardwareDetected_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
@@ -474,6 +514,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) {
+            super.getEnrolledFaces_enforcePermission();
+
             if (userId != UserHandle.getCallingUserId()) {
                 Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
             }
@@ -490,6 +532,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
+            super.hasEnrolledFaces_enforcePermission();
+
             if (userId != UserHandle.getCallingUserId()) {
                 Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
             }
@@ -506,6 +550,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
+            super.getLockoutModeForUser_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getLockoutModeForUser");
@@ -519,6 +565,8 @@
         @Override
         public void invalidateAuthenticatorId(int sensorId, int userId,
                 IInvalidationCallback callback) {
+            super.invalidateAuthenticatorId_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
@@ -531,6 +579,8 @@
         @Override // Binder call
         public long getAuthenticatorId(int sensorId, int userId) {
 
+            super.getAuthenticatorId_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getAuthenticatorId");
@@ -544,6 +594,8 @@
         @Override // Binder call
         public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
                 String opPackageName) {
+            super.resetLockout_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
@@ -558,6 +610,8 @@
         public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
                 final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
                 final String opPackageName) {
+            super.setFeature_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for setFeature");
@@ -572,6 +626,8 @@
         @Override
         public void getFeature(final IBinder token, int userId, int feature,
                 IFaceServiceReceiver receiver, final String opPackageName) {
+            super.getFeature_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getFeature");
@@ -615,6 +671,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         public void registerAuthenticators(
                 @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
+            super.registerAuthenticators_enforcePermission();
+
             mRegistry.registerAll(() -> {
                 final List<ServiceProvider> providers = new ArrayList<>();
                 for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index cfbb5dc..7a13c91 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -139,6 +139,8 @@
     @Override
     public void setTestHalEnabled(boolean enabled) {
 
+        super.setTestHalEnabled_enforcePermission();
+
         mProvider.setTestHalEnabled(enabled);
         mSensor.setTestHalEnabled(enabled);
     }
@@ -147,6 +149,8 @@
     @Override
     public void startEnroll(int userId) {
 
+        super.startEnroll_enforcePermission();
+
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
                 null /* previewSurface */, false /* debugConsent */);
@@ -156,6 +160,8 @@
     @Override
     public void finishEnroll(int userId) {
 
+        super.finishEnroll_enforcePermission();
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -171,6 +177,8 @@
     public void acceptAuthentication(int userId)  {
 
         // Fake authentication with any of the existing faces
+        super.acceptAuthentication_enforcePermission();
+
         List<Face> faces = FaceUtils.getInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (faces.isEmpty()) {
@@ -186,6 +194,8 @@
     @Override
     public void rejectAuthentication(int userId)  {
 
+        super.rejectAuthentication_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
     }
 
@@ -194,6 +204,8 @@
     @Override
     public void notifyAcquired(int userId, int acquireInfo) {
 
+        super.notifyAcquired_enforcePermission();
+
         BaseFrame data = new BaseFrame();
         data.acquiredInfo = (byte) acquireInfo;
 
@@ -210,6 +222,8 @@
     @Override
     public void notifyError(int userId, int errorCode)  {
 
+        super.notifyError_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
                 0 /* vendorCode */);
     }
@@ -218,6 +232,8 @@
     @Override
     public void cleanupInternalState(int userId)  {
 
+        super.cleanupInternalState_enforcePermission();
+
         Slog.d(TAG, "cleanupInternalState: " + userId);
         mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 7a6a274f..151ffaa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -130,6 +130,8 @@
     @Override
     public void setTestHalEnabled(boolean enabled) {
 
+        super.setTestHalEnabled_enforcePermission();
+
         mFace10.setTestHalEnabled(enabled);
     }
 
@@ -137,6 +139,8 @@
     @Override
     public void startEnroll(int userId) {
 
+        super.startEnroll_enforcePermission();
+
         mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
                 null /* previewSurface */, false /* debugConsent */);
@@ -146,6 +150,8 @@
     @Override
     public void finishEnroll(int userId) {
 
+        super.finishEnroll_enforcePermission();
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -161,6 +167,8 @@
     public void acceptAuthentication(int userId) {
 
         // Fake authentication with any of the existing fingers
+        super.acceptAuthentication_enforcePermission();
+
         List<Face> faces = FaceUtils.getLegacyInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (faces.isEmpty()) {
@@ -176,6 +184,8 @@
     @Override
     public void rejectAuthentication(int userId) {
 
+        super.rejectAuthentication_enforcePermission();
+
         mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* faceId */, userId, null);
     }
 
@@ -183,6 +193,8 @@
     @Override
     public void notifyAcquired(int userId, int acquireInfo) {
 
+        super.notifyAcquired_enforcePermission();
+
         mHalResultController.onAcquired(0 /* deviceId */, userId, acquireInfo, 0 /* vendorCode */);
     }
 
@@ -190,6 +202,8 @@
     @Override
     public void notifyError(int userId, int errorCode) {
 
+        super.notifyError_enforcePermission();
+
         mHalResultController.onError(0 /* deviceId */, userId, errorCode, 0 /* vendorCode */);
     }
 
@@ -197,6 +211,8 @@
     @Override
     public void cleanupInternalState(int userId) {
 
+        super.cleanupInternalState_enforcePermission();
+
         mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
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 156e6bb..70470e9 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
@@ -133,6 +133,8 @@
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
+            super.createTestSession_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
 
             if (provider == null) {
@@ -146,6 +148,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
+            super.dumpSensorServiceStateProto_enforcePermission();
+
             final ProtoOutputStream proto = new ProtoOutputStream();
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider != null) {
@@ -169,6 +173,8 @@
         @Override
         public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
                 @NonNull String opPackageName) {
+            super.getSensorProperties_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
@@ -182,6 +188,8 @@
         @Override // Binder call
         public void generateChallenge(IBinder token, int sensorId, int userId,
                 IFingerprintServiceReceiver receiver, String opPackageName) {
+            super.generateChallenge_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
@@ -195,6 +203,8 @@
         @Override // Binder call
         public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                 long challenge) {
+            super.revokeChallenge_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
@@ -210,6 +220,8 @@
         public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
                 final int userId, final IFingerprintServiceReceiver receiver,
                 final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
+            super.enroll_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
@@ -223,6 +235,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override // Binder call
         public void cancelEnrollment(final IBinder token, long requestId) {
+            super.cancelEnrollment_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelEnrollment");
@@ -399,6 +413,8 @@
         @Override
         public long detectFingerprint(final IBinder token, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
+            super.detectFingerprint_enforcePermission();
+
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
                 return -1;
@@ -427,6 +443,8 @@
         public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
                 int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
                 long requestId, int cookie, boolean allowBackgroundAuthentication) {
+            super.prepareForAuthentication_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
@@ -443,6 +461,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
         public void startPreparedClient(int sensorId, int cookie) {
+            super.startPreparedClient_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for startPreparedClient");
@@ -486,6 +506,8 @@
         @Override // Binder call
         public void cancelFingerprintDetect(final IBinder token, final String opPackageName,
                 final long requestId) {
+            super.cancelFingerprintDetect_enforcePermission();
+
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
                         + opPackageName);
@@ -507,6 +529,8 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
                 final String opPackageName, final long requestId) {
+            super.cancelAuthenticationFromService_enforcePermission();
+
             Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);
 
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
@@ -522,6 +546,8 @@
         @Override // Binder call
         public void remove(final IBinder token, final int fingerId, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
+            super.remove_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for remove");
@@ -536,6 +562,8 @@
         public void removeAll(final IBinder token, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
 
+            super.removeAll_enforcePermission();
+
             final FingerprintServiceReceiver internalReceiver = new FingerprintServiceReceiver() {
                 int sensorsFinishedRemoving = 0;
                 final int numSensors = getSensorPropertiesInternal(
@@ -568,6 +596,8 @@
         @Override // Binder call
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
+            super.addLockoutResetCallback_enforcePermission();
+
             mLockoutResetDispatcher.addCallback(callback, opPackageName);
         }
 
@@ -651,6 +681,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean isHardwareDetected(int sensorId, String opPackageName) {
+            super.isHardwareDetected_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
@@ -663,6 +695,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override // Binder call
         public void rename(final int fingerId, final int userId, final String name) {
+            super.rename_enforcePermission();
+
             if (!Utils.isCurrentUserOrProfile(getContext(), userId)) {
                 return;
             }
@@ -718,6 +752,8 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
+            super.hasEnrolledFingerprints_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
@@ -730,6 +766,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
+            super.getLockoutModeForUser_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getLockoutModeForUser");
@@ -742,6 +780,8 @@
         @Override
         public void invalidateAuthenticatorId(int sensorId, int userId,
                 IInvalidationCallback callback) {
+            super.invalidateAuthenticatorId_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
@@ -753,6 +793,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public long getAuthenticatorId(int sensorId, int userId) {
+            super.getAuthenticatorId_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getAuthenticatorId");
@@ -765,6 +807,8 @@
         @Override // Binder call
         public void resetLockout(IBinder token, int sensorId, int userId,
                 @Nullable byte[] hardwareAuthToken, String opPackageName) {
+            super.resetLockout_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
@@ -777,18 +821,24 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override
         public boolean isClientActive() {
+            super.isClientActive_enforcePermission();
+
             return mGestureAvailabilityDispatcher.isAnySensorActive();
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override
         public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
+            super.addClientActiveCallback_enforcePermission();
+
             mGestureAvailabilityDispatcher.registerCallback(callback);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override
         public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
+            super.removeClientActiveCallback_enforcePermission();
+
             mGestureAvailabilityDispatcher.removeCallback(callback);
         }
 
@@ -796,6 +846,8 @@
         @Override // Binder call
         public void registerAuthenticators(
                 @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
+            super.registerAuthenticators_enforcePermission();
+
             mRegistry.registerAll(() -> {
                 final List<ServiceProvider> providers = new ArrayList<>();
                 providers.addAll(getHidlProviders(hidlSensors));
@@ -814,12 +866,16 @@
         @Override
         public void addAuthenticatorsRegisteredCallback(
                 IFingerprintAuthenticatorsRegisteredCallback callback) {
+            super.addAuthenticatorsRegisteredCallback_enforcePermission();
+
             mRegistry.addAllRegisteredCallback(callback);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+            super.registerBiometricStateListener_enforcePermission();
+
             mBiometricStateCallback.registerBiometricStateListener(listener);
         }
 
@@ -827,6 +883,8 @@
         @Override
         public void onPointerDown(long requestId, int sensorId, int x, int y,
                 float minor, float major) {
+            super.onPointerDown_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
@@ -838,6 +896,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPointerUp(long requestId, int sensorId) {
+            super.onPointerUp_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
@@ -849,6 +909,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onUiReady(long requestId, int sensorId) {
+            super.onUiReady_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
@@ -860,6 +922,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
+            super.setUdfpsOverlayController_enforcePermission();
+
             for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setUdfpsOverlayController(controller);
             }
@@ -868,6 +932,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setSidefpsController(@NonNull ISidefpsController controller) {
+            super.setSidefpsController_enforcePermission();
+
             for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setSidefpsController(controller);
             }
@@ -884,6 +950,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPowerPressed() {
+            super.onPowerPressed_enforcePermission();
+
             for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.onPowerPressed();
             }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 4181b99..135eccf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -135,6 +135,8 @@
     @Override
     public void setTestHalEnabled(boolean enabled) {
 
+        super.setTestHalEnabled_enforcePermission();
+
         mProvider.setTestHalEnabled(enabled);
         mSensor.setTestHalEnabled(enabled);
     }
@@ -143,6 +145,8 @@
     @Override
     public void startEnroll(int userId) {
 
+        super.startEnroll_enforcePermission();
+
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
     }
@@ -151,6 +155,8 @@
     @Override
     public void finishEnroll(int userId) {
 
+        super.finishEnroll_enforcePermission();
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -166,6 +172,8 @@
     public void acceptAuthentication(int userId)  {
 
         // Fake authentication with any of the existing fingers
+        super.acceptAuthentication_enforcePermission();
+
         List<Fingerprint> fingerprints = FingerprintUtils.getInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (fingerprints.isEmpty()) {
@@ -181,6 +189,8 @@
     @Override
     public void rejectAuthentication(int userId)  {
 
+        super.rejectAuthentication_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
     }
 
@@ -188,6 +198,8 @@
     @Override
     public void notifyAcquired(int userId, int acquireInfo)  {
 
+        super.notifyAcquired_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback()
                 .onAcquired((byte) acquireInfo, 0 /* vendorCode */);
     }
@@ -196,6 +208,8 @@
     @Override
     public void notifyError(int userId, int errorCode)  {
 
+        super.notifyError_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
                 0 /* vendorCode */);
     }
@@ -204,6 +218,8 @@
     @Override
     public void cleanupInternalState(int userId)  {
 
+        super.cleanupInternalState_enforcePermission();
+
         Slog.d(TAG, "cleanupInternalState: " + userId);
         mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 7f1fb1c..49bd44b 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
@@ -72,7 +72,6 @@
 class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
         implements Udfps, LockoutConsumer, PowerPressHandler {
     private static final String TAG = "FingerprintAuthenticationClient";
-    private static final int MESSAGE_IGNORE_AUTH = 1;
     private static final int MESSAGE_AUTH_SUCCESS = 2;
     private static final int MESSAGE_FINGER_UP = 3;
     @NonNull
@@ -249,12 +248,6 @@
                 () -> {
                     long delay = 0;
                     if (authenticated && mSensorProps.isAnySidefpsType()) {
-                        if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
-                            Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
-                            onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
-                                    0, true);
-                            return;
-                        }
                         delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
 
                         if (mSideFpsLastAcquireStartTime != -1) {
@@ -515,18 +508,15 @@
         if (mSensorProps.isAnySidefpsType()) {
             Slog.i(TAG, "(sideFPS): onPowerPressed");
             mHandler.post(() -> {
-                if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
-                    Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
-                    mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
-                    // Do not call onError() as that will send an additional callback to coex.
-                    onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
-                    mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
-                            mBiometricStrength, getSensorId(), getRequestId());
-                }
-                mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
-                mHandler.postDelayed(() -> {
-                }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
-
+                Slog.i(TAG, "(sideFPS): finishing auth");
+                // Ignore auths after a power has been detected
+                mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+                // Do not call onError() as that will send an additional callback to coex.
+                onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+                        0, true);
+                mSensorOverlays.hide(getSensorId());
+                mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
+                        mBiometricStrength, getSensorId(), getRequestId());
             });
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 682c005..86a9f79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -136,6 +136,8 @@
     @Override
     public void setTestHalEnabled(boolean enabled) {
 
+        super.setTestHalEnabled_enforcePermission();
+
         mFingerprint21.setTestHalEnabled(enabled);
     }
 
@@ -143,6 +145,8 @@
     @Override
     public void startEnroll(int userId) {
 
+        super.startEnroll_enforcePermission();
+
         mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
     }
@@ -151,6 +155,8 @@
     @Override
     public void finishEnroll(int userId) {
 
+        super.finishEnroll_enforcePermission();
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -166,6 +172,8 @@
     public void acceptAuthentication(int userId)  {
 
         // Fake authentication with any of the existing fingers
+        super.acceptAuthentication_enforcePermission();
+
         List<Fingerprint> fingerprints = FingerprintUtils.getLegacyInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (fingerprints.isEmpty()) {
@@ -181,6 +189,8 @@
     @Override
     public void rejectAuthentication(int userId)  {
 
+        super.rejectAuthentication_enforcePermission();
+
         mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null);
     }
 
@@ -188,6 +198,8 @@
     @Override
     public void notifyAcquired(int userId, int acquireInfo)  {
 
+        super.notifyAcquired_enforcePermission();
+
         mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */);
     }
 
@@ -195,6 +207,8 @@
     @Override
     public void notifyError(int userId, int errorCode)  {
 
+        super.notifyError_enforcePermission();
+
         mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */);
     }
 
@@ -202,6 +216,8 @@
     @Override
     public void cleanupInternalState(int userId)  {
 
+        super.cleanupInternalState_enforcePermission();
+
         mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index ff1e762..35ea36c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -63,6 +63,8 @@
             // to wait, and some of the operations below might take a significant amount of time to
             // complete (calls to the HALs). To avoid blocking the rest of system server we put
             // this on a background thread.
+            super.registerAuthenticators_enforcePermission();
+
             final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
                     true /* allowIo */);
             thread.start();
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 11eb782..b882c47 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -841,6 +841,7 @@
                     streamProtos[i].histogramCounts = streamStats.getHistogramCounts();
                     streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile();
                     streamProtos[i].streamUseCase = streamStats.getStreamUseCase();
+                    streamProtos[i].colorSpace = streamStats.getColorSpace();
 
                     if (CameraServiceProxy.DEBUG) {
                         String histogramTypeName =
@@ -863,7 +864,8 @@
                                 + ", histogramCounts "
                                 + Arrays.toString(streamProtos[i].histogramCounts)
                                 + ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile
-                                + ", streamUseCase " + streamProtos[i].streamUseCase);
+                                + ", streamUseCase " + streamProtos[i].streamUseCase
+                                + ", colorSpace " + streamProtos[i].colorSpace);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 387e00f..2c83c6f 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -95,6 +95,8 @@
     @Override
     @EnforcePermission(LOG_COMPAT_CHANGE)
     public void reportChange(long changeId, ApplicationInfo appInfo) {
+        super.reportChange_enforcePermission();
+
         reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED);
     }
 
@@ -102,6 +104,8 @@
     @EnforcePermission(LOG_COMPAT_CHANGE)
     public void reportChangeByPackageName(long changeId, String packageName,
             @UserIdInt int userId) {
+        super.reportChangeByPackageName_enforcePermission();
+
         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
         if (appInfo != null) {
             reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED);
@@ -111,6 +115,8 @@
     @Override
     @EnforcePermission(LOG_COMPAT_CHANGE)
     public void reportChangeByUid(long changeId, int uid) {
+        super.reportChangeByUid_enforcePermission();
+
         reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
     }
 
@@ -121,6 +127,8 @@
     @Override
     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
+        super.isChangeEnabled_enforcePermission();
+
         return isChangeEnabledInternal(changeId, appInfo);
     }
 
@@ -128,6 +136,8 @@
     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
     public boolean isChangeEnabledByPackageName(long changeId, String packageName,
             @UserIdInt int userId) {
+        super.isChangeEnabledByPackageName_enforcePermission();
+
         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
         if (appInfo == null) {
             return mCompatConfig.willChangeBeEnabled(changeId, packageName);
@@ -138,6 +148,8 @@
     @Override
     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
     public boolean isChangeEnabledByUid(long changeId, int uid) {
+        super.isChangeEnabledByUid_enforcePermission();
+
         String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
         if (packages == null || packages.length == 0) {
             return mCompatConfig.defaultChangeIdValue(changeId);
@@ -199,6 +211,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
+        super.setOverrides_enforcePermission();
+
         Map<Long, PackageOverride> overridesMap = new HashMap<>();
         for (long change : overrides.enabledChanges()) {
             overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
@@ -215,6 +229,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
+        super.setOverridesForTest_enforcePermission();
+
         Map<Long, PackageOverride> overridesMap = new HashMap<>();
         for (long change : overrides.enabledChanges()) {
             overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
@@ -231,6 +247,8 @@
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
     public void putAllOverridesOnReleaseBuilds(
             CompatibilityOverridesByPackageConfig overridesByPackage) {
+        super.putAllOverridesOnReleaseBuilds_enforcePermission();
+
         for (CompatibilityOverrideConfig overrides :
                 overridesByPackage.packageNameToOverrides.values()) {
             checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
@@ -242,6 +260,8 @@
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
     public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides,
             String packageName) {
+        super.putOverridesOnReleaseBuilds_enforcePermission();
+
         checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
         mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true);
     }
@@ -249,6 +269,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public int enableTargetSdkChanges(String packageName, int targetSdkVersion) {
+        super.enableTargetSdkChanges_enforcePermission();
+
         int numChanges =
                 mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion);
         killPackage(packageName);
@@ -258,6 +280,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public int disableTargetSdkChanges(String packageName, int targetSdkVersion) {
+        super.disableTargetSdkChanges_enforcePermission();
+
         int numChanges =
                 mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion);
         killPackage(packageName);
@@ -267,6 +291,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void clearOverrides(String packageName) {
+        super.clearOverrides_enforcePermission();
+
         mCompatConfig.removePackageOverrides(packageName);
         killPackage(packageName);
     }
@@ -274,12 +300,16 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void clearOverridesForTest(String packageName) {
+        super.clearOverridesForTest_enforcePermission();
+
         mCompatConfig.removePackageOverrides(packageName);
     }
 
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public boolean clearOverride(long changeId, String packageName) {
+        super.clearOverride_enforcePermission();
+
         boolean existed = mCompatConfig.removeOverride(changeId, packageName);
         killPackage(packageName);
         return existed;
@@ -288,6 +318,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public boolean clearOverrideForTest(long changeId, String packageName) {
+        super.clearOverrideForTest_enforcePermission();
+
         return mCompatConfig.removeOverride(changeId, packageName);
     }
 
@@ -295,6 +327,8 @@
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
     public void removeAllOverridesOnReleaseBuilds(
             CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) {
+        super.removeAllOverridesOnReleaseBuilds_enforcePermission();
+
         for (CompatibilityOverridesToRemoveConfig overridesToRemove :
                 overridesToRemoveByPackage.packageNameToOverridesToRemove.values()) {
             checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
@@ -307,6 +341,8 @@
     public void removeOverridesOnReleaseBuilds(
             CompatibilityOverridesToRemoveConfig overridesToRemove,
             String packageName) {
+        super.removeOverridesOnReleaseBuilds_enforcePermission();
+
         checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
         mCompatConfig.removePackageOverrides(overridesToRemove, packageName);
     }
@@ -314,12 +350,16 @@
     @Override
     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
     public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
+        super.getAppConfig_enforcePermission();
+
         return mCompatConfig.getAppConfig(appInfo);
     }
 
     @Override
     @EnforcePermission(READ_COMPAT_CHANGE_CONFIG)
     public CompatibilityChangeInfo[] listAllChanges() {
+        super.listAllChanges_enforcePermission();
+
         return mCompatConfig.dumpChanges();
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index aa9f2dc..726326f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -33,7 +33,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IThermalEventListener;
@@ -2359,39 +2359,39 @@
         }
     }
 
-    private class UdfpsObserver extends IUdfpsHbmListener.Stub {
-        private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray();
+    private class UdfpsObserver extends IUdfpsRefreshRateRequestCallback.Stub {
+        private final SparseBooleanArray mUdfpsRefreshRateEnabled = new SparseBooleanArray();
 
         public void observe() {
             StatusBarManagerInternal statusBar =
                     LocalServices.getService(StatusBarManagerInternal.class);
             if (statusBar != null) {
-                statusBar.setUdfpsHbmListener(this);
+                statusBar.setUdfpsRefreshRateCallback(this);
             }
         }
 
         @Override
-        public void onHbmEnabled(int displayId) {
+        public void onRequestEnabled(int displayId) {
             synchronized (mLock) {
-                updateHbmStateLocked(displayId, true /*enabled*/);
+                updateRefreshRateStateLocked(displayId, true /*enabled*/);
             }
         }
 
         @Override
-        public void onHbmDisabled(int displayId) {
+        public void onRequestDisabled(int displayId) {
             synchronized (mLock) {
-                updateHbmStateLocked(displayId, false /*enabled*/);
+                updateRefreshRateStateLocked(displayId, false /*enabled*/);
             }
         }
 
-        private void updateHbmStateLocked(int displayId, boolean enabled) {
-            mLocalHbmEnabled.put(displayId, enabled);
+        private void updateRefreshRateStateLocked(int displayId, boolean enabled) {
+            mUdfpsRefreshRateEnabled.put(displayId, enabled);
             updateVoteLocked(displayId);
         }
 
         private void updateVoteLocked(int displayId) {
             final Vote vote;
-            if (mLocalHbmEnabled.get(displayId)) {
+            if (mUdfpsRefreshRateEnabled.get(displayId)) {
                 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
                 float maxRefreshRate = 0f;
                 for (Display.Mode mode : modes) {
@@ -2409,10 +2409,10 @@
 
         void dumpLocked(PrintWriter pw) {
             pw.println("  UdfpsObserver");
-            pw.println("    mLocalHbmEnabled: ");
-            for (int i = 0; i < mLocalHbmEnabled.size(); i++) {
-                final int displayId = mLocalHbmEnabled.keyAt(i);
-                final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
+            pw.println("    mUdfpsRefreshRateEnabled: ");
+            for (int i = 0; i < mUdfpsRefreshRateEnabled.size(); i++) {
+                final int displayId = mUdfpsRefreshRateEnabled.keyAt(i);
+                final String enabled = mUdfpsRefreshRateEnabled.valueAt(i) ? "enabled" : "disabled";
                 pw.println("      Display " + displayId + ": " + enabled);
             }
         }
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 21a8518..5824887 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1682,6 +1682,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean isSaturationActivated() {
+            super.isSaturationActivated_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return !mGlobalSaturationTintController.isActivatedStateNotSet()
@@ -1694,6 +1696,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setAppSaturationLevel(String packageName, int level) {
+            super.setAppSaturationLevel_enforcePermission();
+
             final String callingPackageName = LocalServices.getService(PackageManagerInternal.class)
                     .getNameForUid(Binder.getCallingUid());
             final long token = Binder.clearCallingIdentity();
@@ -1706,6 +1710,8 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         public int getTransformCapabilities() {
+            super.getTransformCapabilities_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return getTransformCapabilitiesInternal();
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 4ca4817..87327cb 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -211,7 +211,7 @@
         mDozeConfig = new AmbientDisplayConfiguration(mContext);
         mUiEventLogger = new UiEventLoggerImpl();
         mDreamUiEventLogger = new DreamUiEventLoggerImpl(
-                mContext.getResources().getString(R.string.config_loggable_dream_prefix));
+                mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
         AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
         mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
         mDreamsOnlyEnabledForSystemUser =
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
index 26ca74a..96ebcbb 100644
--- a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
@@ -26,10 +26,10 @@
  * @hide
  */
 public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
-    final String mLoggableDreamPrefix;
+    private final String[] mLoggableDreamPrefixes;
 
-    DreamUiEventLoggerImpl(String loggableDreamPrefix) {
-        mLoggableDreamPrefix = loggableDreamPrefix;
+    DreamUiEventLoggerImpl(String[] loggableDreamPrefixes) {
+        mLoggableDreamPrefixes = loggableDreamPrefixes;
     }
 
     @Override
@@ -38,13 +38,20 @@
         if (eventID <= 0) {
             return;
         }
-        final boolean isFirstPartyDream =
-                mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith(
-                        mLoggableDreamPrefix);
         FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED,
                 /* uid = 1 */ 0,
                 /* event_id = 2 */ eventID,
                 /* instance_id = 3 */ 0,
-                /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other");
+                /* dream_component_name = 4 */
+                isFirstPartyDream(dreamComponentName) ? dreamComponentName : "other");
+    }
+
+    private boolean isFirstPartyDream(String dreamComponentName) {
+        for (int i = 0; i < mLoggableDreamPrefixes.length; ++i) {
+            if (dreamComponentName.startsWith(mLoggableDreamPrefixes[i])) {
+                return true;
+            }
+        }
+        return false;
     }
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 326d720..2817d1b 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -68,6 +68,8 @@
     @RequiresPermission(Manifest.permission.UPDATE_FONTS)
     @Override
     public FontConfig getFontConfig() {
+        super.getFontConfig_enforcePermission();
+
         return getSystemFontConfig();
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1225d99..fcdb1f5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3925,10 +3925,11 @@
 
     @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
-    public void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode,
-            int displayId) {
+    public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
         // Always call subtype picker, because subtype picker is a superset of input method
         // picker.
+        super.showInputMethodPickerFromSystem_enforcePermission();
+
         mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
                 .sendToTarget();
     }
@@ -3938,6 +3939,8 @@
      */
     @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShownForTest() {
+        super.isInputMethodPickerShownForTest_enforcePermission();
+
         synchronized (ImfLock.class) {
             return mMenuController.isisInputMethodPickerShownForTestLocked();
         }
@@ -4217,6 +4220,8 @@
     @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     @Override
     public void removeImeSurface() {
+        super.removeImeSurface_enforcePermission();
+
         mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
     }
 
@@ -4416,6 +4421,8 @@
     @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
     @Override
     public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
+        super.addVirtualStylusIdForTestSession_enforcePermission();
+
         int uid = Binder.getCallingUid();
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession")) {
@@ -4441,6 +4448,8 @@
     @Override
     public void setStylusWindowIdleTimeoutForTest(
             IInputMethodClient client, @DurationMillisLong long timeout) {
+        super.setStylusWindowIdleTimeoutForTest_enforcePermission();
+
         int uid = Binder.getCallingUid();
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) {
@@ -4538,6 +4547,8 @@
     @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
     @Override
     public void startImeTrace() {
+        super.startImeTrace_enforcePermission();
+
         ImeTracing.getInstance().startTrace(null /* printwriter */);
         ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
@@ -4554,6 +4565,8 @@
     @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
     @Override
     public void stopImeTrace() {
+        super.stopImeTrace_enforcePermission();
+
         ImeTracing.getInstance().stopTrace(null /* printwriter */);
         ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 9bd48f2..dcec0aa 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -950,6 +950,8 @@
     @Override
     public void injectLocation(Location location) {
 
+        super.injectLocation_enforcePermission();
+
         Preconditions.checkArgument(location.isComplete());
 
         int userId = UserHandle.getCallingUserId();
@@ -1160,6 +1162,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public void setExtraLocationControllerPackage(String packageName) {
+        super.setExtraLocationControllerPackage_enforcePermission();
+
         synchronized (mLock) {
             mExtraLocationControllerPackage = packageName;
         }
@@ -1175,6 +1179,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public void setExtraLocationControllerPackageEnabled(boolean enabled) {
+        super.setExtraLocationControllerPackageEnabled_enforcePermission();
+
         synchronized (mLock) {
             mExtraLocationControllerPackageEnabled = enabled;
         }
@@ -1234,6 +1240,8 @@
     @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
     public void setAutomotiveGnssSuspended(boolean suspended) {
 
+        super.setAutomotiveGnssSuspended_enforcePermission();
+
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             throw new IllegalStateException(
                     "setAutomotiveGnssSuspended only allowed on automotive devices");
@@ -1247,6 +1255,8 @@
     @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
     public boolean isAutomotiveGnssSuspended() {
 
+        super.isAutomotiveGnssSuspended_enforcePermission();
+
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             throw new IllegalStateException(
                     "isAutomotiveGnssSuspended only allowed on automotive devices");
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 6232028..90245b5e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -207,6 +207,14 @@
             handleClientMessageCallback(mContextHubId, hostEndpointId, message, nanoappPermissions,
                     messagePermissions);
         }
+
+        @Override
+        public void handleServiceRestart() {
+            Log.i(TAG, "Starting Context Hub Service restart");
+            initExistingCallbacks();
+            resetSettings();
+            Log.i(TAG, "Finished Context Hub Service restart");
+        }
     }
 
     public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) {
@@ -381,6 +389,20 @@
     }
 
     /**
+     * Initializes existing callbacks with the mContextHubWrapper for every context hub
+     */
+    private void initExistingCallbacks() {
+        for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
+            try {
+                mContextHubWrapper.registerExistingCallback(contextHubId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while registering existing service callback for hub "
+                        + "(ID = " + contextHubId + ")", e);
+            }
+        }
+    }
+
+    /**
      * Handles the initialization of location settings notifications
      */
     private void initLocationSettingNotifications() {
@@ -508,6 +530,17 @@
         mContext.registerReceiver(btReceiver, filter);
     }
 
+    /**
+     * Resets the settings. Called when a context hub restarts or the AIDL HAL dies
+     */
+    private void resetSettings() {
+        sendLocationSettingUpdate();
+        sendWifiSettingUpdate(/* forceUpdate= */ true);
+        sendAirplaneModeSettingUpdate();
+        sendMicrophoneDisableSettingUpdateForCurrentUser();
+        sendBtSettingUpdate(/* forceUpdate= */ true);
+    }
+
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback callback, ResultReceiver result) {
@@ -517,6 +550,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public int registerCallback(IContextHubCallback callback) throws RemoteException {
+        super.registerCallback_enforcePermission();
+
         mCallbacksList.register(callback);
 
         Log.d(TAG, "Added callback, total callbacks " +
@@ -527,12 +562,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public int[] getContextHubHandles() throws RemoteException {
+        super.getContextHubHandles_enforcePermission();
+
         return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet());
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
+        super.getContextHubInfo_enforcePermission();
+
         if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) {
             Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo");
             return null;
@@ -549,6 +588,8 @@
      */
     @Override
     public List<ContextHubInfo> getContextHubs() throws RemoteException {
+        super.getContextHubs_enforcePermission();
+
         return mContextHubInfoList;
     }
 
@@ -615,6 +656,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException {
+        super.loadNanoApp_enforcePermission();
+
         if (mContextHubWrapper == null) {
             return -1;
         }
@@ -642,6 +685,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public int unloadNanoApp(int nanoAppHandle) throws RemoteException {
+        super.unloadNanoApp_enforcePermission();
+
         if (mContextHubWrapper == null) {
             return -1;
         }
@@ -668,6 +713,8 @@
     @Override
     public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) throws RemoteException {
 
+        super.getNanoAppInstanceInfo_enforcePermission();
+
         return mNanoAppStateManager.getNanoAppInstanceInfo(nanoAppHandle);
     }
 
@@ -676,6 +723,8 @@
     public int[] findNanoAppOnHub(
             int contextHubHandle, NanoAppFilter filter) throws RemoteException {
 
+        super.findNanoAppOnHub_enforcePermission();
+
         ArrayList<Integer> foundInstances = new ArrayList<>();
         if (filter != null) {
             mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> {
@@ -720,6 +769,8 @@
     @Override
     public int sendMessage(int contextHubHandle, int nanoAppHandle, ContextHubMessage msg)
             throws RemoteException {
+        super.sendMessage_enforcePermission();
+
         if (mContextHubWrapper == null) {
             return -1;
         }
@@ -841,11 +892,7 @@
 
             ContextHubEventLogger.getInstance().logContextHubRestart(contextHubId);
 
-            sendLocationSettingUpdate();
-            sendWifiSettingUpdate(/* forceUpdate= */ true);
-            sendAirplaneModeSettingUpdate();
-            sendMicrophoneDisableSettingUpdateForCurrentUser();
-            sendBtSettingUpdate(/* forceUpdate= */ true);
+            resetSettings();
 
             mTransactionManager.onHubReset();
             queryNanoAppsInternal(contextHubId);
@@ -916,6 +963,8 @@
     public IContextHubClient createClient(
             int contextHubId, IContextHubClientCallback clientCallback,
             @Nullable String attributionTag, String packageName) throws RemoteException {
+        super.createClient_enforcePermission();
+
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
         }
@@ -944,6 +993,8 @@
     public IContextHubClient createPendingIntentClient(
             int contextHubId, PendingIntent pendingIntent, long nanoAppId,
             @Nullable String attributionTag) throws RemoteException {
+        super.createPendingIntentClient_enforcePermission();
+
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
         }
@@ -966,6 +1017,8 @@
     public void loadNanoAppOnHub(
             int contextHubId, IContextHubTransactionCallback transactionCallback,
             NanoAppBinary nanoAppBinary) throws RemoteException {
+        super.loadNanoAppOnHub_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_LOAD_NANOAPP)) {
             return;
@@ -995,6 +1048,8 @@
     public void unloadNanoAppFromHub(
             int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
             throws RemoteException {
+        super.unloadNanoAppFromHub_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_UNLOAD_NANOAPP)) {
             return;
@@ -1018,6 +1073,8 @@
     public void enableNanoApp(
             int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
             throws RemoteException {
+        super.enableNanoApp_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_ENABLE_NANOAPP)) {
             return;
@@ -1041,6 +1098,8 @@
     public void disableNanoApp(
             int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
             throws RemoteException {
+        super.disableNanoApp_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_DISABLE_NANOAPP)) {
             return;
@@ -1062,6 +1121,8 @@
     @Override
     public void queryNanoApps(int contextHubId, IContextHubTransactionCallback transactionCallback)
             throws RemoteException {
+        super.queryNanoApps_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_QUERY_NANOAPPS)) {
             return;
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 432b097..48152b4 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -32,6 +32,7 @@
 import android.hardware.location.NanoAppState;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -92,6 +93,11 @@
          */
         void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
                 List<String> nanoappPermissions, List<String> messagePermissions);
+
+        /**
+         * Handles a restart of the service
+         */
+        void handleServiceRestart();
     }
 
     /**
@@ -170,12 +176,9 @@
     }
 
     /**
-     * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
-     *
-     * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
+     * Attempts to connect to the AIDL HAL and returns the proxy IContextHub.
      */
-    @Nullable
-    public static IContextHubWrapper maybeConnectToAidl() {
+    public static android.hardware.contexthub.IContextHub maybeConnectToAidlGetProxy() {
         android.hardware.contexthub.IContextHub proxy = null;
         final String aidlServiceName =
                 android.hardware.contexthub.IContextHub.class.getCanonicalName() + "/default";
@@ -188,8 +191,18 @@
         } else {
             Log.d(TAG, "Context Hub AIDL service is not declared");
         }
+        return proxy;
+    }
 
-        return (proxy == null) ? null : new ContextHubWrapperAidl(proxy);
+    /**
+     * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
+     *
+     * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
+     */
+    @Nullable
+    public static IContextHubWrapper maybeConnectToAidl() {
+        android.hardware.contexthub.IContextHub proxy = maybeConnectToAidlGetProxy();
+        return proxy == null ? null : new ContextHubWrapperAidl(proxy);
     }
 
     /**
@@ -354,12 +367,22 @@
     public abstract void registerCallback(int contextHubId, @NonNull ICallback callback)
             throws RemoteException;
 
-    private static class ContextHubWrapperAidl extends IContextHubWrapper {
+    /**
+     * Registers an existing callback with the Context Hub.
+     *
+     * @param contextHubId The ID of the Context Hub to register the callback with.
+     */
+    public abstract void registerExistingCallback(int contextHubId) throws RemoteException;
+
+    private static class ContextHubWrapperAidl extends IContextHubWrapper
+            implements IBinder.DeathRecipient {
         private android.hardware.contexthub.IContextHub mHub;
 
         private final Map<Integer, ContextHubAidlCallback> mAidlCallbackMap =
                     new HashMap<>();
 
+        private Runnable mHandleServiceRestartCallback = null;
+
         // Use this thread in case where the execution requires to be on a service thread.
         // For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission.
         private HandlerThread mHandlerThread =
@@ -419,17 +442,51 @@
         }
 
         ContextHubWrapperAidl(android.hardware.contexthub.IContextHub hub) {
-            mHub = hub;
+            setHub(hub);
             mHandlerThread.start();
             mHandler = new Handler(mHandlerThread.getLooper());
+            linkWrapperToHubDeath();
+        }
+
+        private synchronized android.hardware.contexthub.IContextHub getHub() {
+            return mHub;
+        }
+
+        private synchronized void setHub(android.hardware.contexthub.IContextHub hub) {
+            mHub = hub;
+        }
+
+        @Override
+        public void binderDied() {
+            Log.i(TAG, "Context Hub AIDL HAL died");
+
+            setHub(maybeConnectToAidlGetProxy());
+            if (getHub() == null) {
+                // TODO(b/256860015): Make this reconnection more robust
+                Log.e(TAG, "Could not reconnect to Context Hub AIDL HAL");
+                return;
+            }
+            linkWrapperToHubDeath();
+
+            if (mHandleServiceRestartCallback != null) {
+                mHandleServiceRestartCallback.run();
+            } else {
+                Log.e(TAG, "mHandleServiceRestartCallback is not set");
+            }
         }
 
         public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
+                        new ArrayList<String>());
+            }
+
             Set<String> supportedPermissions = new HashSet<>();
             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
-            for (android.hardware.contexthub.ContextHubInfo hub : mHub.getContextHubs()) {
-                hubInfoList.add(new ContextHubInfo(hub));
-                for (String permission : hub.supportedPermissions) {
+            for (android.hardware.contexthub.ContextHubInfo hubInfo : hub.getContextHubs()) {
+                hubInfoList.add(new ContextHubInfo(hubInfo));
+                for (String permission : hubInfo.supportedPermissions) {
                     supportedPermissions.add(permission);
                 }
             }
@@ -489,8 +546,13 @@
 
         @Override
         public void onHostEndpointConnected(HostEndpointInfo info) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
             try {
-                mHub.onHostEndpointConnected(info);
+                hub.onHostEndpointConnected(info);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage());
             }
@@ -498,8 +560,13 @@
 
         @Override
         public void onHostEndpointDisconnected(short hostEndpointId) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
             try {
-                mHub.onHostEndpointDisconnected((char) hostEndpointId);
+                hub.onHostEndpointDisconnected((char) hostEndpointId);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage());
             }
@@ -509,8 +576,13 @@
         public int sendMessageToContextHub(
                 short hostEndpointId, int contextHubId, NanoAppMessage message)
                 throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.sendMessageToHub(contextHubId,
+                hub.sendMessageToHub(contextHubId,
                         ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message));
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException e) {
@@ -523,10 +595,15 @@
         @ContextHubTransaction.Result
         public int loadNanoapp(int contextHubId, NanoAppBinary binary,
                 int transactionId) throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             android.hardware.contexthub.NanoappBinary aidlNanoAppBinary =
                     ContextHubServiceUtil.createAidlNanoAppBinary(binary);
             try {
-                mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
+                hub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -538,8 +615,13 @@
         @ContextHubTransaction.Result
         public int unloadNanoapp(int contextHubId, long nanoappId, int transactionId)
                 throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.unloadNanoapp(contextHubId, nanoappId, transactionId);
+                hub.unloadNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -551,8 +633,13 @@
         @ContextHubTransaction.Result
         public int enableNanoapp(int contextHubId, long nanoappId, int transactionId)
                 throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.enableNanoapp(contextHubId, nanoappId, transactionId);
+                hub.enableNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -564,8 +651,13 @@
         @ContextHubTransaction.Result
         public int disableNanoapp(int contextHubId, long nanoappId, int transactionId)
                 throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.disableNanoapp(contextHubId, nanoappId, transactionId);
+                hub.disableNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -576,8 +668,13 @@
 
         @ContextHubTransaction.Result
         public int queryNanoapps(int contextHubId) throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.queryNanoapps(contextHubId);
+                hub.queryNanoapps(contextHubId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -586,22 +683,65 @@
             }
         }
 
-        public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
-            mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+        public void registerExistingCallback(int contextHubId) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
+            ContextHubAidlCallback callback = mAidlCallbackMap.get(contextHubId);
+            if (callback == null) {
+                Log.e(TAG, "Could not find existing callback to register for context hub ID = "
+                        + contextHubId);
+                return;
+            }
+
             try {
-                mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
+                hub.registerCallback(contextHubId, callback);
             } catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) {
                 Log.e(TAG, "Exception while registering callback: " + e.getMessage());
             }
         }
 
+        public void registerCallback(int contextHubId, ICallback callback) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
+            mHandleServiceRestartCallback = callback::handleServiceRestart;
+            mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+            registerExistingCallback(contextHubId);
+        }
+
         private void onSettingChanged(byte setting, boolean enabled) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
             try {
-                mHub.onSettingChanged(setting, enabled);
+                hub.onSettingChanged(setting, enabled);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "Exception while sending setting update: " + e.getMessage());
             }
         }
+
+        /**
+         * Links the mHub death handler to this
+         */
+        private void linkWrapperToHubDeath() {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
+            try {
+                hub.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException exception) {
+                Log.e(TAG, "Context Hub AIDL service death receipt could not be linked");
+            }
+        }
     }
 
     /**
@@ -729,6 +869,17 @@
             mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId));
         }
 
+        public void registerExistingCallback(int contextHubId) throws RemoteException {
+            ContextHubWrapperHidlCallback callback = mHidlCallbackMap.get(contextHubId);
+            if (callback == null) {
+                Log.e(TAG, "Could not find existing callback for context hub with ID = "
+                        + contextHubId);
+                return;
+            }
+
+            mHub.registerCallback(contextHubId, callback);
+        }
+
         public boolean supportsBtSettingNotifications() {
             return false;
         }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index c67d54f..f5ec880 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -131,8 +131,6 @@
                             UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
                 }
             }
-
-            mEventLogger.enqueue(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
         }
     };
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index eb37ceb..5dcbb16 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3422,6 +3422,8 @@
         @Override
         public void setToastRateLimitingEnabled(boolean enable) {
 
+            super.setToastRateLimitingEnabled_enforcePermission();
+
             synchronized (mToastQueue) {
                 int uid = Binder.getCallingUid();
                 int userId = UserHandle.getUserId(uid);
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 6735d55..bac8916 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -120,6 +120,8 @@
         @Nullable
         @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
         public String getLockName() {
+            super.getLockName_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return mOemLock.getLockName();
@@ -131,6 +133,8 @@
         @Override
         @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
         public void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+            super.setOemUnlockAllowedByCarrier_enforcePermission();
+
             enforceUserIsAdmin();
 
             final long token = Binder.clearCallingIdentity();
@@ -144,6 +148,8 @@
         @Override
         @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
         public boolean isOemUnlockAllowedByCarrier() {
+            super.isOemUnlockAllowedByCarrier_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
               return mOemLock.isOemUnlockAllowedByCarrier();
@@ -157,6 +163,8 @@
         @Override
         @EnforcePermission(MANAGE_USER_OEM_UNLOCK_STATE)
         public void setOemUnlockAllowedByUser(boolean allowedByUser) {
+            super.setOemUnlockAllowedByUser_enforcePermission();
+
             if (ActivityManager.isUserAMonkey()) {
                 // Prevent a monkey from changing this
                 return;
@@ -183,6 +191,8 @@
         @Override
         @EnforcePermission(MANAGE_USER_OEM_UNLOCK_STATE)
         public boolean isOemUnlockAllowedByUser() {
+            super.isOemUnlockAllowedByUser_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return mOemLock.isOemUnlockAllowedByDevice();
@@ -199,6 +209,8 @@
         @Override
         @EnforcePermission(anyOf = {READ_OEM_UNLOCK_STATE, OEM_UNLOCK_STATE})
         public boolean isOemUnlockAllowed() {
+            super.isOemUnlockAllowed_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 boolean allowed = mOemLock.isOemUnlockAllowedByCarrier()
@@ -213,6 +225,8 @@
         @Override
         @EnforcePermission(anyOf = {READ_OEM_UNLOCK_STATE, OEM_UNLOCK_STATE})
         public boolean isDeviceOemUnlocked() {
+            super.isDeviceOemUnlocked_enforcePermission();
+
             String locked = SystemProperties.get(FLASH_LOCK_PROP);
             switch (locked) {
                 case FLASH_LOCK_UNLOCKED:
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index baa471c..3421eb7 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -55,10 +55,12 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.res.ApkAssets;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FabricatedOverlayInternal;
@@ -880,14 +882,14 @@
                     }
                     Slog.d(TAG, "commit failed: " + e.getMessage(), e);
                     throw new SecurityException("commit failed"
-                            + (DEBUG ? ": " + e.getMessage() : ""));
+                            + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
                 }
             } finally {
                 traceEnd(TRACE_TAG_RRO);
             }
         }
 
-        private Set<PackageAndUser> executeRequest(
+        private Set<UserPackage> executeRequest(
                 @NonNull final OverlayManagerTransaction.Request request)
                 throws OperationFailedException {
             Objects.requireNonNull(request, "Transaction contains a null request");
@@ -932,7 +934,7 @@
             try {
                 switch (request.type) {
                     case TYPE_SET_ENABLED:
-                        Set<PackageAndUser> result = null;
+                        Set<UserPackage> result = null;
                         result = CollectionUtils.addAll(result,
                                 mImpl.setEnabled(request.overlay, true, realUserId));
                         result = CollectionUtils.addAll(result,
@@ -973,7 +975,7 @@
 
             synchronized (mLock) {
                 // execute the requests (as calling user)
-                Set<PackageAndUser> affectedPackagesToUpdate = null;
+                Set<UserPackage> affectedPackagesToUpdate = null;
                 for (final OverlayManagerTransaction.Request request : transaction) {
                     affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
                             executeRequest(request));
@@ -1370,13 +1372,13 @@
         }
     }
 
-    private void updateTargetPackagesLocked(@Nullable PackageAndUser updatedTarget) {
+    private void updateTargetPackagesLocked(@Nullable UserPackage updatedTarget) {
         if (updatedTarget != null) {
             updateTargetPackagesLocked(Set.of(updatedTarget));
         }
     }
 
-    private void updateTargetPackagesLocked(@Nullable Set<PackageAndUser> updatedTargets) {
+    private void updateTargetPackagesLocked(@Nullable Set<UserPackage> updatedTargets) {
         if (CollectionUtils.isEmpty(updatedTargets)) {
             return;
         }
@@ -1405,7 +1407,7 @@
 
     @Nullable
     private static SparseArray<ArraySet<String>> groupTargetsByUserId(
-            @Nullable final Set<PackageAndUser> targetsAndUsers) {
+            @Nullable final Set<UserPackage> targetsAndUsers) {
         final SparseArray<ArraySet<String>> userTargets = new SparseArray<>();
         CollectionUtils.forEach(targetsAndUsers, target -> {
             ArraySet<String> targets = userTargets.get(target.userId);
@@ -1472,7 +1474,7 @@
 
     @NonNull
     private SparseArray<List<String>> updatePackageManagerLocked(
-            @Nullable Set<PackageAndUser> targets) {
+            @Nullable Set<UserPackage> targets) {
         if (CollectionUtils.isEmpty(targets)) {
             return new SparseArray<>();
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 17bb39c..6ffe60d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -35,6 +35,7 @@
 import android.content.om.CriticalOverlayInfo;
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.os.FabricatedOverlayInfo;
@@ -154,13 +155,13 @@
      * set of targets that had, but no longer have, active overlays.
      */
     @NonNull
-    ArraySet<PackageAndUser> updateOverlaysForUser(final int newUserId) {
+    ArraySet<UserPackage> updateOverlaysForUser(final int newUserId) {
         if (DEBUG) {
             Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId);
         }
 
         // Remove the settings of all overlays that are no longer installed for this user.
-        final ArraySet<PackageAndUser> updatedTargets = new ArraySet<>();
+        final ArraySet<UserPackage> updatedTargets = new ArraySet<>();
         final ArrayMap<String, AndroidPackage> userPackages = mPackageManager.initializeForUser(
                 newUserId);
         CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(
@@ -185,7 +186,7 @@
                 // When a new user is switched to for the first time, package manager must be
                 // informed of the overlay paths for all overlaid packages installed in the user.
                 if (overlaidByOthers.contains(pkg.getPackageName())) {
-                    updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId));
+                    updatedTargets.add(UserPackage.of(newUserId, pkg.getPackageName()));
                 }
             } catch (OperationFailedException e) {
                 Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()
@@ -237,7 +238,7 @@
                     mSettings.setEnabled(overlay, newUserId, true);
                     if (updateState(oi, newUserId, 0)) {
                         CollectionUtils.add(updatedTargets,
-                                new PackageAndUser(oi.targetPackageName, oi.userId));
+                                UserPackage.of(oi.userId, oi.targetPackageName));
                     }
                 }
             } catch (OverlayManagerSettings.BadKeyException e) {
@@ -258,40 +259,40 @@
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
+    Set<UserPackage> onPackageAdded(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
-        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        final Set<UserPackage> updatedTargets = new ArraySet<>();
         // Always update the overlays of newly added packages.
-        updatedTargets.add(new PackageAndUser(pkgName, userId));
+        updatedTargets.add(UserPackage.of(userId, pkgName));
         updatedTargets.addAll(reconcileSettingsForPackage(pkgName, userId, 0 /* flags */));
         return updatedTargets;
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageChanged(@NonNull final String pkgName,
+    Set<UserPackage> onPackageChanged(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
         return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageReplacing(@NonNull final String pkgName, final int userId)
+    Set<UserPackage> onPackageReplacing(@NonNull final String pkgName, final int userId)
             throws OperationFailedException {
         return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageReplaced(@NonNull final String pkgName, final int userId)
+    Set<UserPackage> onPackageReplaced(@NonNull final String pkgName, final int userId)
             throws OperationFailedException {
         return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageRemoved(@NonNull final String pkgName, final int userId) {
+    Set<UserPackage> onPackageRemoved(@NonNull final String pkgName, final int userId) {
         if (DEBUG) {
             Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId);
         }
         // Update the state of all overlays that target this package.
-        final Set<PackageAndUser> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
+        final Set<UserPackage> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
 
         // Remove all the overlays this package declares.
         return CollectionUtils.addAll(targets,
@@ -299,15 +300,15 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> removeOverlaysForUser(
+    private Set<UserPackage> removeOverlaysForUser(
             @NonNull final Predicate<OverlayInfo> condition, final int userId) {
         final List<OverlayInfo> overlays = mSettings.removeIf(
                 io -> userId == io.userId && condition.test(io));
-        Set<PackageAndUser> targets = Collections.emptySet();
+        Set<UserPackage> targets = Collections.emptySet();
         for (int i = 0, n = overlays.size(); i < n; i++) {
             final OverlayInfo info = overlays.get(i);
             targets = CollectionUtils.add(targets,
-                    new PackageAndUser(info.targetPackageName, userId));
+                    UserPackage.of(userId, info.targetPackageName));
 
             // Remove the idmap if the overlay is no longer installed for any user.
             removeIdmapIfPossible(info);
@@ -316,7 +317,7 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> updateOverlaysForTarget(@NonNull final String targetPackage,
+    private Set<UserPackage> updateOverlaysForTarget(@NonNull final String targetPackage,
             final int userId, final int flags) {
         boolean modified = false;
         final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackage, userId);
@@ -332,18 +333,18 @@
         if (!modified) {
             return Collections.emptySet();
         }
-        return Set.of(new PackageAndUser(targetPackage, userId));
+        return Set.of(UserPackage.of(userId, targetPackage));
     }
 
     @NonNull
-    private Set<PackageAndUser> updatePackageOverlays(@NonNull AndroidPackage pkg,
+    private Set<UserPackage> updatePackageOverlays(@NonNull AndroidPackage pkg,
             final int userId, final int flags) throws OperationFailedException {
         if (pkg.getOverlayTarget() == null) {
             // This package does not have overlays declared in its manifest.
             return Collections.emptySet();
         }
 
-        Set<PackageAndUser> updatedTargets = Collections.emptySet();
+        Set<UserPackage> updatedTargets = Collections.emptySet();
         final OverlayIdentifier overlay = new OverlayIdentifier(pkg.getPackageName());
         final int priority = getPackageConfiguredPriority(pkg);
         try {
@@ -353,7 +354,7 @@
                     // If the targetPackageName has changed, the package that *used* to
                     // be the target must also update its assets.
                     updatedTargets = CollectionUtils.add(updatedTargets,
-                            new PackageAndUser(currentInfo.targetPackageName, userId));
+                            UserPackage.of(userId, currentInfo.targetPackageName));
                 }
 
                 currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(),
@@ -367,13 +368,13 @@
                 // reinitialized. Reorder the overlay and update its target package.
                 mSettings.setPriority(overlay, userId, priority);
                 updatedTargets = CollectionUtils.add(updatedTargets,
-                        new PackageAndUser(currentInfo.targetPackageName, userId));
+                        UserPackage.of(userId, currentInfo.targetPackageName));
             }
 
             // Update the enabled state of the overlay.
             if (updateState(currentInfo, userId, flags)) {
                 updatedTargets = CollectionUtils.add(updatedTargets,
-                        new PackageAndUser(currentInfo.targetPackageName, userId));
+                        UserPackage.of(userId, currentInfo.targetPackageName));
             }
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
@@ -382,14 +383,14 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> reconcileSettingsForPackage(@NonNull final String pkgName,
+    private Set<UserPackage> reconcileSettingsForPackage(@NonNull final String pkgName,
             final int userId, final int flags) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "reconcileSettingsForPackage pkgName=" + pkgName + " userId=" + userId);
         }
 
         // Update the state of overlays that target this package.
-        Set<PackageAndUser> updatedTargets = Collections.emptySet();
+        Set<UserPackage> updatedTargets = Collections.emptySet();
         updatedTargets = CollectionUtils.addAll(updatedTargets,
                 updateOverlaysForTarget(pkgName, userId, flags));
 
@@ -423,7 +424,7 @@
     }
 
     @NonNull
-    Set<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay,
+    Set<UserPackage> setEnabled(@NonNull final OverlayIdentifier overlay,
             final boolean enable, final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d",
@@ -442,7 +443,7 @@
             modified |= updateState(oi, userId, 0);
 
             if (modified) {
-                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+                return Set.of(UserPackage.of(userId, oi.targetPackageName));
             }
             return Set.of();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -450,7 +451,7 @@
         }
     }
 
-    Optional<PackageAndUser> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
+    Optional<UserPackage> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
             boolean withinCategory, final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, String.format("setEnabledExclusive overlay=%s"
@@ -493,7 +494,7 @@
             modified |= updateState(enabledInfo, userId, 0);
 
             if (modified) {
-                return Optional.of(new PackageAndUser(enabledInfo.targetPackageName, userId));
+                return Optional.of(UserPackage.of(userId, enabledInfo.targetPackageName));
             }
             return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -502,7 +503,7 @@
     }
 
     @NonNull
-    Set<PackageAndUser> registerFabricatedOverlay(
+    Set<UserPackage> registerFabricatedOverlay(
             @NonNull final FabricatedOverlayInternal overlay)
             throws OperationFailedException {
         if (FrameworkParsingPackageUtils.validateName(overlay.overlayName,
@@ -516,7 +517,7 @@
             throw new OperationFailedException("failed to create fabricated overlay");
         }
 
-        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        final Set<UserPackage> updatedTargets = new ArraySet<>();
         for (int userId : mSettings.getUsers()) {
             updatedTargets.addAll(registerFabricatedOverlay(info, userId));
         }
@@ -524,13 +525,13 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> registerFabricatedOverlay(
+    private Set<UserPackage> registerFabricatedOverlay(
             @NonNull final FabricatedOverlayInfo info, int userId)
             throws OperationFailedException {
         final OverlayIdentifier overlayIdentifier = new OverlayIdentifier(
                 info.packageName, info.overlayName);
 
-        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        final Set<UserPackage> updatedTargets = new ArraySet<>();
         OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId);
         if (oi != null) {
             if (!oi.isFabricated) {
@@ -543,7 +544,7 @@
                 if (oi != null) {
                     // If the fabricated overlay changes its target package, update the previous
                     // target package so it no longer is overlaid.
-                    updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+                    updatedTargets.add(UserPackage.of(userId, oi.targetPackageName));
                 }
                 oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName,
                         info.targetOverlayable, info.path, true, false,
@@ -554,7 +555,7 @@
                 mSettings.setBaseCodePath(overlayIdentifier, userId, info.path);
             }
             if (updateState(oi, userId, 0)) {
-                updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+                updatedTargets.add(UserPackage.of(userId, oi.targetPackageName));
             }
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
@@ -564,8 +565,8 @@
     }
 
     @NonNull
-    Set<PackageAndUser> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
-        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+    Set<UserPackage> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
+        final Set<UserPackage> updatedTargets = new ArraySet<>();
         for (int userId : mSettings.getUsers()) {
             updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId));
         }
@@ -573,7 +574,7 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> unregisterFabricatedOverlay(
+    private Set<UserPackage> unregisterFabricatedOverlay(
             @NonNull final OverlayIdentifier overlay, int userId) {
         final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId);
         if (oi != null) {
@@ -581,7 +582,7 @@
             if (oi.isEnabled()) {
                 // Removing a fabricated overlay only changes the overlay path of a package if it is
                 // currently enabled.
-                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+                return Set.of(UserPackage.of(userId, oi.targetPackageName));
             }
         }
         return Set.of();
@@ -627,7 +628,7 @@
         return mOverlayConfig.isEnabled(overlay.getPackageName());
     }
 
-    Optional<PackageAndUser> setPriority(@NonNull final OverlayIdentifier overlay,
+    Optional<UserPackage> setPriority(@NonNull final OverlayIdentifier overlay,
             @NonNull final OverlayIdentifier newParentOverlay, final int userId)
             throws OperationFailedException {
         try {
@@ -644,7 +645,7 @@
             }
 
             if (mSettings.setPriority(overlay, newParentOverlay, userId)) {
-                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+                return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName));
             }
             return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -652,7 +653,7 @@
         }
     }
 
-    Set<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay,
+    Set<UserPackage> setHighestPriority(@NonNull final OverlayIdentifier overlay,
             final int userId) throws OperationFailedException {
         try{
             if (DEBUG) {
@@ -667,7 +668,7 @@
             }
 
             if (mSettings.setHighestPriority(overlay, userId)) {
-                return Set.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+                return Set.of(UserPackage.of(userId, overlayInfo.targetPackageName));
             }
             return Set.of();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -675,7 +676,7 @@
         }
     }
 
-    Optional<PackageAndUser> setLowestPriority(@NonNull final OverlayIdentifier overlay,
+    Optional<UserPackage> setLowestPriority(@NonNull final OverlayIdentifier overlay,
             final int userId) throws OperationFailedException {
         try{
             if (DEBUG) {
@@ -690,7 +691,7 @@
             }
 
             if (mSettings.setLowestPriority(overlay, userId)) {
-                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+                return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName));
             }
             return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
diff --git a/services/core/java/com/android/server/pm/CloneProfileResolver.java b/services/core/java/com/android/server/pm/CloneProfileResolver.java
new file mode 100644
index 0000000..d3113e1
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CloneProfileResolver.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content.Intent;
+import android.content.pm.ResolveInfo;
+
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.resolution.ComponentResolverApi;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Cross Profile intent resolution strategy used for and to clone profile.
+ */
+public class CloneProfileResolver extends CrossProfileResolver {
+
+    public CloneProfileResolver(ComponentResolverApi componentResolver,
+            UserManagerService userManagerService) {
+        super(componentResolver, userManagerService);
+    }
+
+    /**
+     * This is resolution strategy for Clone Profile.
+     * In case of clone profile, the profile is supposed to be transparent to end user. To end user
+     * clone and owner profile should be part of same user space. Hence, the resolution strategy
+     * would resolve intent in both profile and return combined result without any filtering of the
+     * results.
+     *
+     * @param computer ComputerEngine instance that would be needed by ComponentResolverApi
+     * @param intent request
+     * @param resolvedType the MIME data type of intent request
+     * @param userId source/initiating user
+     * @param targetUserId target user id
+     * @param flags of intent request
+     * @param pkgName the application package name this Intent is limited to
+     * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
+     *                                                        targeting the targetUserId
+     * @param hasNonNegativePriorityResult if source have any non-negative(active and valid)
+     *                                     resolveInfo in their profile.
+     * @param pkgSettingFunction function to find PackageStateInternal for given package
+     * @return list of {@link CrossProfileDomainInfo}
+     */
+    @Override
+    public List<CrossProfileDomainInfo> resolveIntent(Computer computer, Intent intent,
+            String resolvedType, int userId, int targetUserId, long flags,
+            String pkgName, List<CrossProfileIntentFilter> matchingFilters,
+            boolean hasNonNegativePriorityResult,
+            Function<String, PackageStateInternal> pkgSettingFunction) {
+        List<ResolveInfo> resolveInfos = mComponentResolver.queryActivities(computer,
+                intent, resolvedType, flags, targetUserId);
+        List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
+        if (resolveInfos != null) {
+
+            for (int index = 0; index < resolveInfos.size(); index++) {
+                crossProfileDomainInfos.add(new CrossProfileDomainInfo(resolveInfos.get(index),
+                        DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE,
+                        targetUserId));
+            }
+        }
+        return filterIfNotSystemUser(crossProfileDomainInfos, userId);
+    }
+
+    /**
+     * As clone and owner profile are going to be part of the same userspace, we need no filtering
+     * out of any clone profile's result
+     * @param intent request
+     * @param crossProfileDomainInfos resolved in target user
+     * @param flags for intent resolution
+     * @param sourceUserId source user
+     * @param targetUserId target user
+     * @param highestApprovalLevel highest level of domain approval
+     * @return list of CrossProfileDomainInfo
+     */
+    @Override
+    public List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity(Intent intent,
+            List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags, int sourceUserId,
+            int targetUserId, int highestApprovalLevel) {
+        // no filtering for clone profile
+        return crossProfileDomainInfos;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index d381e32..b285136 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -460,7 +460,7 @@
         mBackgroundDexOptService = args.service.mBackgroundDexOptService;
         mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
         mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine(
-                mUserManager, mDomainVerificationManager, mDefaultAppProvider);
+                mUserManager, mDomainVerificationManager, mDefaultAppProvider, mContext);
 
         // Used to reference PMS attributes that are primitives and which are not
         // updated under control of the PMS lock.
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 0cd698a..798217f 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -46,6 +46,9 @@
     private static final String ATTR_FILTER = "filter";
     private static final String ATTR_ACCESS_CONTROL = "accessControl";
 
+    //flag to decide if intent needs to be resolved cross profile if pkgName is already defined
+    public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008;
+
     private static final String TAG = "CrossProfileIntentFilter";
 
     /**
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
index 9ea16d3..2581878 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static com.android.server.pm.CrossProfileIntentFilter.FLAG_IS_PACKAGE_FOR_FILTER;
+
 import android.annotation.NonNull;
 import android.content.IntentFilter;
 
@@ -37,7 +39,7 @@
 
     @Override
     protected boolean isPackageForFilter(String packageName, CrossProfileIntentFilter filter) {
-        return false;
+        return (FLAG_IS_PACKAGE_FOR_FILTER & filter.mFlags) != 0;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index 7752fdf..5ae4cab 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -25,12 +25,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.os.Process;
 import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -54,13 +56,15 @@
     private final UserManagerService mUserManager;
     private final DomainVerificationManagerInternal mDomainVerificationManager;
     private final DefaultAppProvider mDefaultAppProvider;
+    private final Context mContext;
 
     public CrossProfileIntentResolverEngine(UserManagerService userManager,
             DomainVerificationManagerInternal domainVerificationManager,
-            DefaultAppProvider defaultAppProvider) {
+            DefaultAppProvider defaultAppProvider, Context context) {
         mUserManager = userManager;
         mDomainVerificationManager = domainVerificationManager;
         mDefaultAppProvider = defaultAppProvider;
+        mContext = context;
     }
 
     /**
@@ -196,6 +200,21 @@
     @SuppressWarnings("unused")
     private CrossProfileResolver chooseCrossProfileResolver(@NonNull Computer computer,
             UserInfo sourceUserInfo, UserInfo targetUserInfo) {
+        //todo change isCloneProfile to user properties b/241532322
+        /**
+         * If source or target user is clone profile, using {@link CloneProfileResolver}
+         * We would allow CloneProfileResolver only if flag
+         * SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE is enabled
+         */
+        if (sourceUserInfo.isCloneProfile() || targetUserInfo.isCloneProfile()) {
+            if (FeatureFlagUtils.isEnabled(mContext,
+                    FeatureFlagUtils.SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE)) {
+                return new CloneProfileResolver(computer.getComponentResolver(),
+                        mUserManager);
+            } else {
+                return null;
+            }
+        }
         return new DefaultCrossProfileResolver(computer.getComponentResolver(),
                 mUserManager, mDomainVerificationManager);
     }
@@ -313,8 +332,6 @@
         }
 
         if (pkgName == null && intent.hasWebURI()) {
-            // If instant apps are not allowed and there is result only from current or cross
-            // profile return it
             if (!addInstant && ((candidates.size() <= 1 && crossProfileCandidates.isEmpty())
                     || (candidates.isEmpty() && !crossProfileCandidates.isEmpty()))) {
                 candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b979e7a..8089af3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -107,6 +107,7 @@
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.TestUtilityService;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.overlay.OverlayPaths;
@@ -2986,6 +2987,7 @@
     @Override
     public void notifyPackageRemoved(String packageName, int uid) {
         mPackageObserverHelper.notifyRemoved(packageName, uid);
+        UserPackage.removeFromCache(UserHandle.getUserId(uid), packageName);
     }
 
     void sendPackageAddedForUser(@NonNull Computer snapshot, String packageName,
@@ -5061,8 +5063,11 @@
                     "getUnsuspendablePackagesForUser");
             final int callingUid = Binder.getCallingUid();
             if (UserHandle.getUserId(callingUid) != userId) {
-                throw new SecurityException("Calling uid " + callingUid
-                        + " cannot query getUnsuspendablePackagesForUser for user " + userId);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                        "Calling uid " + callingUid
+                                + " cannot query getUnsuspendablePackagesForUser for user "
+                                + userId);
             }
             return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(),
                     packageNames, userId, callingUid);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 0d99075..00f7dc4 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -20,6 +20,7 @@
 import android.annotation.UserIdInt;
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
+import android.content.pm.UserPackage;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -30,7 +31,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.pm.ShortcutService.DumpFilter;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import libcore.io.IoUtils;
 
@@ -70,7 +70,7 @@
     /**
      * Package name -> IDs.
      */
-    final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
+    private final ArrayMap<UserPackage, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
 
     private ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
             @UserIdInt int ownerUserId, @NonNull String packageName,
@@ -101,12 +101,12 @@
      * Called when the new package can't receive the backup, due to signature or version mismatch.
      */
     private void onRestoreBlocked() {
-        final ArrayList<PackageWithUser> pinnedPackages =
+        final ArrayList<UserPackage> pinnedPackages =
                 new ArrayList<>(mPinnedShortcuts.keySet());
         mPinnedShortcuts.clear();
         for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
-            final PackageWithUser pu = pinnedPackages.get(i);
-            final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(pu.packageName);
+            final UserPackage up = pinnedPackages.get(i);
+            final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(up.packageName);
             if (p != null) {
                 p.refreshPinnedFlags();
             }
@@ -135,13 +135,13 @@
             return; // No need to instantiate.
         }
 
-        final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
+        final UserPackage up = UserPackage.of(packageUserId, packageName);
 
         final int idSize = ids.size();
         if (idSize == 0) {
-            mPinnedShortcuts.remove(pu);
+            mPinnedShortcuts.remove(up);
         } else {
-            final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
+            final ArraySet<String> prevSet = mPinnedShortcuts.get(up);
 
             // Actually pin shortcuts.
             // This logic here is to make sure a launcher cannot pin a shortcut that is not dynamic
@@ -165,7 +165,7 @@
                     newSet.add(id);
                 }
             }
-            mPinnedShortcuts.put(pu, newSet);
+            mPinnedShortcuts.put(up, newSet);
         }
         packageShortcuts.refreshPinnedFlags();
     }
@@ -176,7 +176,7 @@
     @Nullable
     public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
             @UserIdInt int packageUserId) {
-        return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
+        return mPinnedShortcuts.get(UserPackage.of(packageUserId, packageName));
     }
 
     /**
@@ -207,7 +207,7 @@
     }
 
     boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
-        return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
+        return mPinnedShortcuts.remove(UserPackage.of(packageUserId, packageName)) != null;
     }
 
     public void ensurePackageInfo() {
@@ -241,15 +241,15 @@
         getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
 
         for (int i = 0; i < size; i++) {
-            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+            final UserPackage up = mPinnedShortcuts.keyAt(i);
 
-            if (forBackup && (pu.userId != getOwnerUserId())) {
+            if (forBackup && (up.userId != getOwnerUserId())) {
                 continue; // Target package on a different user, skip. (i.e. work profile)
             }
 
             out.startTag(null, TAG_PACKAGE);
-            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
-            ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
+            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, up.packageName);
+            ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, up.userId);
 
             final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
             final int idSize = ids.size();
@@ -345,7 +345,7 @@
                                 ATTR_PACKAGE_USER_ID, ownerUserId);
                         ids = new ArraySet<>();
                         ret.mPinnedShortcuts.put(
-                                PackageWithUser.of(packageUserId, packageName), ids);
+                                UserPackage.of(packageUserId, packageName), ids);
                         continue;
                     }
                 }
@@ -386,14 +386,14 @@
         for (int i = 0; i < size; i++) {
             pw.println();
 
-            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+            final UserPackage up = mPinnedShortcuts.keyAt(i);
 
             pw.print(prefix);
             pw.print("  ");
             pw.print("Package: ");
-            pw.print(pu.packageName);
+            pw.print(up.packageName);
             pw.print("  User: ");
-            pw.println(pu.userId);
+            pw.println(up.userId);
 
             final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
             final int idSize = ids.size();
@@ -418,7 +418,7 @@
 
     @VisibleForTesting
     ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
-        return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
+        return new ArraySet<>(mPinnedShortcuts.get(UserPackage.of(packageUserId, packageName)));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index b6f09ff..83720f1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -57,6 +57,7 @@
 import android.content.pm.ShortcutManager;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
@@ -118,7 +119,6 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 import com.android.server.uri.UriGrantsManagerInternal;
 
 import libcore.io.IoUtils;
@@ -3774,7 +3774,7 @@
 
         final long start = getStatStartTime();
         try {
-            final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
+            final ArrayList<UserPackage> gonePackages = new ArrayList<>();
 
             synchronized (mLock) {
                 final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
@@ -3789,13 +3789,14 @@
                             Slog.d(TAG, "Uninstalled: " + spi.getPackageName()
                                     + " user " + spi.getPackageUserId());
                         }
-                        gonePackages.add(PackageWithUser.of(spi));
+                        gonePackages.add(
+                                UserPackage.of(spi.getPackageUserId(), spi.getPackageName()));
                     }
                 });
                 if (gonePackages.size() > 0) {
                     for (int i = gonePackages.size() - 1; i >= 0; i--) {
-                        final PackageWithUser pu = gonePackages.get(i);
-                        cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId,
+                        final UserPackage up = gonePackages.get(i);
+                        cleanUpPackageLocked(up.packageName, ownerUserId, up.userId,
                                 /* appStillExists = */ false);
                     }
                 }
@@ -5274,7 +5275,7 @@
             final ShortcutUser user = mUsers.get(userId);
             if (user == null) return null;
 
-            return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName));
+            return user.getAllLaunchersForTest().get(UserPackage.of(userId, packageName));
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 20bbf46..94eb6bb 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -21,6 +21,7 @@
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchSession;
 import android.content.pm.ShortcutManager;
+import android.content.pm.UserPackage;
 import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.FileUtils;
@@ -52,7 +53,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -82,44 +82,6 @@
     private static final String KEY_LAUNCHERS = "launchers";
     private static final String KEY_PACKAGES = "packages";
 
-    static final class PackageWithUser {
-        final int userId;
-        final String packageName;
-
-        private PackageWithUser(int userId, String packageName) {
-            this.userId = userId;
-            this.packageName = Objects.requireNonNull(packageName);
-        }
-
-        public static PackageWithUser of(int userId, String packageName) {
-            return new PackageWithUser(userId, packageName);
-        }
-
-        public static PackageWithUser of(ShortcutPackageItem spi) {
-            return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
-        }
-
-        @Override
-        public int hashCode() {
-            return packageName.hashCode() ^ userId;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof PackageWithUser)) {
-                return false;
-            }
-            final PackageWithUser that = (PackageWithUser) obj;
-
-            return userId == that.userId && packageName.equals(that.packageName);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("[Package: %d, %s]", userId, packageName);
-        }
-    }
-
     final ShortcutService mService;
     final AppSearchManager mAppSearchManager;
     final Executor mExecutor;
@@ -129,7 +91,7 @@
 
     private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
 
-    private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
+    private final ArrayMap<UserPackage, ShortcutLauncher> mLaunchers = new ArrayMap<>();
 
     /** In-memory-cached default launcher. */
     private String mCachedLauncher;
@@ -204,20 +166,20 @@
     // We don't expose this directly to non-test code because only ShortcutUser should add to/
     // remove from it.
     @VisibleForTesting
-    ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
+    ArrayMap<UserPackage, ShortcutLauncher> getAllLaunchersForTest() {
         return mLaunchers;
     }
 
     private void addLauncher(ShortcutLauncher launcher) {
         launcher.replaceUser(this);
-        mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
+        mLaunchers.put(UserPackage.of(launcher.getPackageUserId(),
                 launcher.getPackageName()), launcher);
     }
 
     @Nullable
     public ShortcutLauncher removeLauncher(
             @UserIdInt int packageUserId, @NonNull String packageName) {
-        return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
+        return mLaunchers.remove(UserPackage.of(packageUserId, packageName));
     }
 
     @Nullable
@@ -242,7 +204,7 @@
     @NonNull
     public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
             @UserIdInt int launcherUserId) {
-        final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
+        final UserPackage key = UserPackage.of(launcherUserId, packageName);
         ShortcutLauncher ret = mLaunchers.get(key);
         if (ret == null) {
             ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index aeb11b7..fbfc84a 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -196,8 +196,11 @@
                     appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags,
                             true /* migrateAppData */);
                 }
-            } catch (IllegalStateException e) {
-                // Device was probably ejected, and we'll process that event momentarily
+            } catch (RuntimeException e) {
+                // The volume was probably already unmounted.  We'll probably process the unmount
+                // event momentarily.  TODO(b/256909937): ignoring errors from prepareUserStorage()
+                // is very dangerous.  Instead, we should fix the race condition that allows this
+                // code to run on an unmounted volume in the first place.
                 Slog.w(TAG, "Failed to prepare storage: " + e);
             }
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index cf0ea43..f85b11d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 
 import android.Manifest;
 import android.accounts.Account;
@@ -52,6 +53,7 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserPackage;
 import android.content.pm.UserProperties;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.res.Configuration;
@@ -313,7 +315,7 @@
     @VisibleForTesting
     static class UserData {
         // Basic user information and properties
-        UserInfo info;
+        @NonNull UserInfo info;
         // Account name used when there is a strong association between a user and an account
         String account;
         // Account information for seeding into a newly created user. This could also be
@@ -3267,11 +3269,39 @@
         }
     }
 
+    /** Checks whether the device is currently in headless system user mode (for any reason). */
+    @Override
+    public boolean isHeadlessSystemUserMode() {
+        synchronized (mUsersLock) {
+            final UserData systemUserData = mUsers.get(UserHandle.USER_SYSTEM);
+            return !systemUserData.info.isFull();
+        }
+    }
+
     /**
-     * Checks whether the device is really headless system user mode, ignoring system user mode
-     * emulation.
+     * Checks whether the default state of the device is headless system user mode, i.e. what the
+     * mode would be if we did a fresh factory reset.
+     * If the mode is  being emulated (via SYSTEM_USER_MODE_EMULATION_PROPERTY) then that will be
+     * returned instead.
+     * Note that, even in the absence of emulation, a device might deviate from the current default
+     * due to an OTA changing the default (which won't change the already-decided mode).
      */
-    private boolean isReallyHeadlessSystemUserMode() {
+    private boolean isDefaultHeadlessSystemUserMode() {
+        if (!Build.isDebuggable()) {
+            return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
+        }
+
+        final String emulatedValue = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
+        if (!TextUtils.isEmpty(emulatedValue)) {
+            if (UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS.equals(emulatedValue)) return true;
+            if (UserManager.SYSTEM_USER_MODE_EMULATION_FULL.equals(emulatedValue)) return false;
+            if (!UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT.equals(emulatedValue)) {
+                Slogf.e(LOG_TAG, "isDefaultHeadlessSystemUserMode(): ignoring invalid valued of "
+                                + "property %s: %s",
+                        SYSTEM_USER_MODE_EMULATION_PROPERTY, emulatedValue);
+            }
+        }
+
         return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
     }
 
@@ -3283,30 +3313,11 @@
         if (!Build.isDebuggable()) {
             return;
         }
-
-        final String emulatedValue = SystemProperties
-                .get(UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY);
-        if (TextUtils.isEmpty(emulatedValue)) {
+        if (TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
             return;
         }
 
-        final boolean newHeadlessSystemUserMode;
-        switch (emulatedValue) {
-            case UserManager.SYSTEM_USER_MODE_EMULATION_FULL:
-                newHeadlessSystemUserMode = false;
-                break;
-            case UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS:
-                newHeadlessSystemUserMode = true;
-                break;
-            case UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT:
-                newHeadlessSystemUserMode = isReallyHeadlessSystemUserMode();
-                break;
-            default:
-                Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): ignoring invalid valued of "
-                        + "property %s: %s", UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY,
-                        emulatedValue);
-                return;
-        }
+        final boolean newHeadlessSystemUserMode = isDefaultHeadlessSystemUserMode();
 
         // Update system user type
         synchronized (mPackagesLock) {
@@ -3342,7 +3353,7 @@
             }
         }
 
-        // Update emulated mode, which will used to triger an update on user packages
+        // Update emulated mode, which will used to trigger an update on user packages
         mUpdatingSystemUserMode = true;
     }
 
@@ -3532,7 +3543,11 @@
             synchronized (mUsersLock) {
                 UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
                 userData.info.flags |= UserInfo.FLAG_SYSTEM;
-                if (!UserManager.isHeadlessSystemUserMode()) {
+                // We assume that isDefaultHeadlessSystemUserMode() does not change during the OTA
+                // from userVersion < 8 since it is documented that pre-R devices do not support its
+                // modification. Therefore, its current value should be the same as the pre-update
+                // version.
+                if (!isDefaultHeadlessSystemUserMode()) {
                     userData.info.flags |= UserInfo.FLAG_FULL;
                 }
                 userIdsToWrite.add(userData.info.id);
@@ -3741,7 +3756,7 @@
         int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
                 | UserInfo.FLAG_PRIMARY;
         // Create the system user
-        String systemUserType = UserManager.isHeadlessSystemUserMode()
+        String systemUserType = isDefaultHeadlessSystemUserMode()
                 ? UserManager.USER_TYPE_SYSTEM_HEADLESS
                 : UserManager.USER_TYPE_FULL_SYSTEM;
         flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
@@ -5848,6 +5863,7 @@
                 Slog.d(LOG_TAG, "updateUserIds(): userIds= " + Arrays.toString(mUserIds)
                         + " includingPreCreated=" + Arrays.toString(mUserIdsIncludingPreCreated));
             }
+            UserPackage.setValidUserIds(mUserIds);
         }
     }
 
@@ -6208,9 +6224,12 @@
                 com.android.internal.R.bool.config_guestUserEphemeral));
         pw.println("  Force ephemeral users: " + mForceEphemeralUsers);
         pw.println("  Is split-system user: " + UserManager.isSplitSystemUser());
-        final boolean isHeadlessSystemUserMode = UserManager.isHeadlessSystemUserMode();
+        final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
         pw.println("  Is headless-system mode: " + isHeadlessSystemUserMode);
-        if (isHeadlessSystemUserMode != isReallyHeadlessSystemUserMode()) {
+        if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
+            pw.println("  (differs from the current default build value)");
+        }
+        if (!TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
             pw.println("  (emulated by 'cmd user set-system-user-mode-emulation')");
             if (mUpdatingSystemUserMode) {
                 pw.println("  (and being updated after boot)");
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b98d20e..5b6196f 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -128,7 +128,8 @@
                 .setIsCredentialSharableWithParent(true)
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
-                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT));
     }
 
     /**
@@ -163,7 +164,8 @@
                 .setIsCredentialSharableWithParent(true)
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
-                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 8772de3..a7d4cea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.apex.ApexInfo;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.Attribution;
@@ -79,11 +78,8 @@
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
-import libcore.util.EmptyArray;
-
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -109,21 +105,9 @@
     public static PackageInfo generate(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
-                grantedPermissions, state, userId, null, pkgSetting);
-    }
-
-    /**
-     * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
-     * @deprecated Once ENABLE_FEATURE_SCAN_APEX is removed, this should also be removed.
-     */
-    @Deprecated
-    @Nullable
-    public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, long flags,
-            @Nullable PackageStateInternal pkgSetting, @UserIdInt int userId) {
-        return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
-                PackageUserStateInternal.DEFAULT, userId, apexInfo, pkgSetting);
+                grantedPermissions, state, userId, pkgSetting);
     }
 
     /**
@@ -132,8 +116,7 @@
     private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable ApexInfo apexInfo,
-            @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
                 pkgSetting);
         if (applicationInfo == null) {
@@ -247,22 +230,6 @@
                     &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
         }
 
-        if (apexInfo != null) {
-            File apexFile = new File(apexInfo.modulePath);
-
-            info.applicationInfo.sourceDir = apexFile.getPath();
-            info.applicationInfo.publicSourceDir = apexFile.getPath();
-            info.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
-            info.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
-            if (apexInfo.isFactory) {
-                info.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-            } else {
-                info.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-            }
-            info.isApex = true;
-            info.isActiveApex = apexInfo.isActive;
-        }
-
         final SigningDetails signingDetails = pkg.getSigningDetails();
         // deprecated method of getting signing certificates
         if ((flags & PackageManager.GET_SIGNATURES) != 0) {
@@ -294,7 +261,7 @@
         info.coreApp = pkg.isCoreApp();
         info.isApex = pkg.isApex();
 
-        if (pkgSetting != null && !pkgSetting.hasSharedUser()) {
+        if (!pkgSetting.hasSharedUser()) {
             // It is possible that this shared UID app has left
             info.sharedUserId = null;
             info.sharedUserLabel = 0;
@@ -452,7 +419,7 @@
     public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
             @PackageManager.ApplicationInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @UserIdInt int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (pkg == null) {
             return null;
         }
@@ -463,35 +430,31 @@
         }
 
         // Make shallow copy so we can store the metadata/libraries safely
-        ApplicationInfo info = AndroidPackageUtils.toAppInfoWithoutState(pkg);
+        ApplicationInfo info = AndroidPackageUtils.generateAppInfoWithoutState(pkg);
 
         updateApplicationInfo(info, flags, state);
 
         initForUser(info, pkg, userId);
 
-        if (pkgSetting != null) {
-            // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
-            PackageStateUnserialized pkgState = pkgSetting.getTransientState();
-            info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
-            List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
-            var usesLibraries = pkgState.getUsesLibraryInfos();
-            var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
-            for (int index = 0; index < usesLibraries.size(); index++) {
-                usesLibraryInfos.add(usesLibraries.get(index).getInfo());
-            }
-            info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
-                    ? null : usesLibraryFiles.toArray(new String[0]);
-            info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
-            if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
-                info.category = pkgSetting.getCategoryOverride();
-            }
+        // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
+        PackageStateUnserialized pkgState = pkgSetting.getTransientState();
+        info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
+        List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
+        var usesLibraries = pkgState.getUsesLibraryInfos();
+        var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
+        for (int index = 0; index < usesLibraries.size(); index++) {
+            usesLibraryInfos.add(usesLibraries.get(index).getInfo());
+        }
+        info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+                ? null : usesLibraryFiles.toArray(new String[0]);
+        info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+        if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+            info.category = pkgSetting.getCategoryOverride();
         }
 
         info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
-        info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
-                : pkgSetting.getPrimaryCpuAbi();
-        info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
-                : pkgSetting.getSecondaryCpuAbi();
+        info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+        info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
 
         info.flags |= appInfoFlags(info.flags, pkgSetting);
         info.privateFlags |= appInfoPrivateFlags(info.privateFlags, pkgSetting);
@@ -508,7 +471,7 @@
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
             @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @UserIdInt int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
     }
 
@@ -520,7 +483,7 @@
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
             @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         if (a == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -597,7 +560,7 @@
     @Nullable
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
     }
 
@@ -609,7 +572,7 @@
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @Nullable ApplicationInfo applicationInfo, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (s == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -647,7 +610,7 @@
     public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @NonNull ApplicationInfo applicationInfo, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (p == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -696,7 +659,7 @@
     @Nullable
     public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
             AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags,
-            PackageUserStateInternal state, int userId, @Nullable PackageStateInternal pkgSetting) {
+            PackageUserStateInternal state, int userId, @NonNull PackageStateInternal pkgSetting) {
         if (i == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -719,10 +682,8 @@
 
         initForUser(info, pkg, userId);
 
-        info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
-                : pkgSetting.getPrimaryCpuAbi();
-        info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
-                : pkgSetting.getSecondaryCpuAbi();
+        info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+        info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
         info.nativeLibraryDir = pkg.getNativeLibraryDir();
         info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
 
@@ -820,12 +781,11 @@
      * all uninstalled and hidden packages as well.
      */
     public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
-            PackageStateInternal pkgSetting, PackageUserStateInternal state,
+            @NonNull PackageStateInternal pkgSetting, PackageUserStateInternal state,
             @PackageManager.PackageInfoFlagsBits long flags) {
         // Returns false if the package is hidden system app until installed.
         if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
                 && !state.isInstalled()
-                && pkgSetting != null
                 && pkgSetting.getTransientState().isHiddenUntilInstalled()) {
             return false;
         }
@@ -878,7 +838,7 @@
 
     private static void assignFieldsComponentInfoParsedMainComponent(
             @NonNull ComponentInfo info, @NonNull ParsedMainComponent component,
-            @Nullable PackageStateInternal pkgSetting, int userId) {
+            @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
         assignFieldsComponentInfoParsedMainComponent(info, component);
         Pair<CharSequence, Integer> labelAndIcon =
                 ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -889,7 +849,7 @@
 
     private static void assignFieldsPackageItemInfoParsedComponent(
             @NonNull PackageItemInfo info, @NonNull ParsedComponent component,
-            @Nullable PackageStateInternal pkgSetting, int userId) {
+            @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
         assignFieldsPackageItemInfoParsedComponent(info, component);
         Pair<CharSequence, Integer> labelAndIcon =
                 ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -1141,7 +1101,7 @@
         @Nullable
         public ApplicationInfo generate(AndroidPackage pkg,
                 @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserStateInternal state,
-                int userId, @Nullable PackageStateInternal pkgSetting) {
+                int userId, @NonNull PackageStateInternal pkgSetting) {
             ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
             if (appInfo != null) {
                 return appInfo;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index a6f1b29..5b0cc51 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -321,8 +321,4 @@
         info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
         info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
     }
-
-    public static ApplicationInfo toAppInfoWithoutState(AndroidPackage pkg) {
-        return ((ParsingPackageHidden) pkg).toAppInfoWithoutState();
-    }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 3b9f0ba..1fa3b3b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -406,6 +406,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
     @Override
     public void stopOneTimePermissionSession(String packageName, @UserIdInt int userId) {
+        super.stopOneTimePermissionSession_enforcePermission();
+
         Objects.requireNonNull(packageName);
 
         final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 61c21e6..ab35dc8 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -35,6 +35,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
+import static android.hardware.SensorPrivacyManager.EXTRA_TOGGLE_TYPE;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 import static android.hardware.SensorPrivacyManager.Sources.DIALOG;
@@ -664,6 +665,29 @@
                             .build());
         }
 
+        private void showSensorStateChangedActivity(@SensorPrivacyManager.Sensors.Sensor int sensor,
+                @SensorPrivacyManager.ToggleType int toggleType) {
+            String activityName = mContext.getResources().getString(
+                    R.string.config_sensorStateChangedActivity);
+            if (TextUtils.isEmpty(activityName)) {
+                return;
+            }
+
+            Intent dialogIntent = new Intent();
+            dialogIntent.setComponent(
+                    ComponentName.unflattenFromString(activityName));
+
+            ActivityOptions options = ActivityOptions.makeBasic();
+            options.setTaskOverlay(true, true);
+
+            dialogIntent.addFlags(
+                    FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_NO_USER_ACTION);
+
+            dialogIntent.putExtra(EXTRA_SENSOR, sensor);
+            dialogIntent.putExtra(EXTRA_TOGGLE_TYPE, toggleType);
+            mContext.startActivityAsUser(dialogIntent, options.toBundle(), UserHandle.SYSTEM);
+        }
+
         private boolean isTelevision(Context context) {
             int uiMode = context.getResources().getConfiguration().uiMode;
             return (uiMode & Configuration.UI_MODE_TYPE_MASK)
@@ -1378,6 +1402,8 @@
                     mToggleSensorListeners.finishBroadcast();
                 }
             }
+
+            mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
         }
 
         public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 4111446..43ffa81 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -18,11 +18,11 @@
 
 import android.annotation.Nullable;
 import android.app.ITransientNotificationCallback;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -158,7 +158,7 @@
     /** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
     void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails);
 
     /** @see com.android.internal.statusbar.IStatusBar#showTransient */
@@ -192,9 +192,10 @@
     void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable);
 
     /**
-     * Sets the system-wide listener for UDFPS HBM status changes.
+     * Sets the system-wide callback for UDFPS refresh rate changes.
      *
-     * @see com.android.internal.statusbar.IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+     * @see com.android.internal.statusbar.IStatusBar#setUdfpsRefreshRateCallback
+     * (IUdfpsRefreshRateRequestCallback)
      */
-    void setUdfpsHbmListener(IUdfpsHbmListener listener);
+    void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index c59b90f..7281a47 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -56,7 +56,7 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.net.Uri;
@@ -83,7 +83,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -175,7 +176,7 @@
 
     private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
     @GuardedBy("mLock")
-    private IUdfpsHbmListener mUdfpsHbmListener;
+    private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
     @GuardedBy("mLock")
     private IBiometricContextListener mBiometricContextListener;
 
@@ -617,15 +618,15 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) {
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
-                    navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                    navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                     letterboxDetails);
             if (mBar != null) {
                 try {
                     mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                            navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                            navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                             letterboxDetails);
                 } catch (RemoteException ex) { }
             }
@@ -694,13 +695,13 @@
         }
 
         @Override
-        public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+        public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
             synchronized (mLock) {
-                mUdfpsHbmListener = listener;
+                mUdfpsRefreshRateRequestCallback = callback;
             }
             if (mBar != null) {
                 try {
-                    mBar.setUdfpsHbmListener(listener);
+                    mBar.setUdfpsRefreshRateCallback(callback);
                 } catch (RemoteException ex) { }
             }
         }
@@ -944,11 +945,11 @@
     }
 
     @Override
-    public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+    public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
         enforceStatusBarService();
         if (mBar != null) {
             try {
-                mBar.setUdfpsHbmListener(listener);
+                mBar.setUdfpsRefreshRateCallback(callback);
             } catch (RemoteException ex) {
             }
         }
@@ -1211,7 +1212,7 @@
         private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
         private boolean mNavbarColorManagedByIme = false;
         private @Behavior int mBehavior;
-        private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+        private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
         private String mPackageName = "none";
         private int mDisabled1 = 0;
         private int mDisabled2 = 0;
@@ -1223,14 +1224,14 @@
 
         private void setBarAttributes(@Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName,
                 LetterboxDetails[] letterboxDetails) {
             mAppearance = appearance;
             mAppearanceRegions = appearanceRegions;
             mNavbarColorManagedByIme = navbarColorManagedByIme;
             mBehavior = behavior;
-            mRequestedVisibilities = requestedVisibilities;
+            mRequestedVisibleTypes = requestedVisibleTypes;
             mPackageName = packageName;
             mLetterboxDetails = letterboxDetails;
         }
@@ -1363,7 +1364,7 @@
                     state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
                     state.mImeBackDisposition, state.mShowImeSwitcher,
                     gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
-                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities,
+                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibleTypes,
                     state.mPackageName, transientBarTypes, state.mLetterboxDetails);
         }
     }
@@ -1374,11 +1375,11 @@
             mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null);
         });
         // If StatusBarService dies, system_server doesn't get killed with it, so we need to make
-        // sure the UDFPS listener is refreshed as well. Deferring to the handler just so to avoid
+        // sure the UDFPS callback is refreshed as well. Deferring to the handler just so to avoid
         // making registerStatusBar re-entrant.
         mHandler.post(() -> {
             synchronized (mLock) {
-                setUdfpsHbmListener(mUdfpsHbmListener);
+                setUdfpsRefreshRateCallback(mUdfpsRefreshRateRequestCallback);
                 setBiometicContextListener(mBiometricContextListener);
             }
         });
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4972412..8d106f7 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -28,7 +28,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -60,10 +60,10 @@
 
     @Override
     public void setConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
-        ConfigurationChangeListener configurationChangeListener =
+            @NonNull StateChangeListener listener) {
+        StateChangeListener stateChangeListener =
                 () -> mHandler.post(listener::onChange);
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
+        mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 773b517..e9827ce 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -25,8 +25,8 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -185,8 +185,7 @@
      * ensure O(1) lookup performance when working out whether a listener should trigger.
      */
     @GuardedBy("mListeners")
-    private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
-            new ArrayMap<>();
+    private final ArrayMap<StateChangeListener, HashSet<String>> mListeners = new ArrayMap<>();
 
     private static final Object SLOCK = new Object();
 
@@ -213,7 +212,7 @@
 
     private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
         synchronized (mListeners) {
-            for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
+            for (Map.Entry<StateChangeListener, HashSet<String>> listenerEntry
                     : mListeners.entrySet()) {
                 // It's unclear which set of the following two Sets is going to be larger in the
                 // average case: monitoredKeys will be a subset of the set of possible keys, but
@@ -249,7 +248,7 @@
      * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no
      * associated remove method.
      */
-    public void addListener(@NonNull ConfigurationChangeListener listener,
+    public void addListener(@NonNull StateChangeListener listener,
             @NonNull Set<String> keys) {
         Objects.requireNonNull(listener);
         Objects.requireNonNull(keys);
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index a39f64c..ff180eb 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -19,7 +19,7 @@
 import android.annotation.UserIdInt;
 import android.app.time.TimeConfiguration;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 /**
  * An interface that provides access to service configuration for time detection. This hides
@@ -33,18 +33,18 @@
      * Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
      * The listener is invoked on the main thread.
      */
-    void addConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+    void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Removes a listener previously added via {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+     * #addConfigurationInternalChangeListener(StateChangeListener)}.
      */
-    void removeConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+    void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
      * snapshot so callers must use {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+     * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
      * changes.
      */
     @NonNull
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 71acf35..4ef713c 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -49,7 +49,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.time.Instant;
 import java.util.ArrayList;
@@ -104,8 +104,8 @@
     @NonNull private final ServerFlagsOriginPrioritiesSupplier mServerFlagsOriginPrioritiesSupplier;
 
     @GuardedBy("this")
-    @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
-            new ArrayList<>();
+    @NonNull
+    private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
 
     /**
      * If a newly calculated system clock time and the current system clock time differs by this or
@@ -167,20 +167,20 @@
     }
 
     private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+        for (StateChangeListener changeListener : mConfigurationInternalListeners) {
             changeListener.onChange();
         }
     }
 
     @Override
     public synchronized void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
     }
 
     @Override
     public synchronized void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
     }
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 3cee19c..13ec753 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -44,8 +44,8 @@
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timezonedetector.ArrayMapWithHistory;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ReferenceWithHistory;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.io.PrintWriter;
 import java.time.Duration;
@@ -136,11 +136,11 @@
     public interface Environment {
 
         /**
-         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
-         * changes that could affect the content of {@link ConfigurationInternal}.
+         * Sets a {@link StateChangeListener} that will be invoked when there are any changes that
+         * could affect the content of {@link ConfigurationInternal}.
          * This is invoked during system server setup.
          */
-        void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+        void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
         /** Returns the {@link ConfigurationInternal} for the current user. */
         @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 8e2a5f4..0409a84 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -26,7 +26,6 @@
 import android.annotation.UserIdInt;
 import android.app.time.Capabilities.CapabilityState;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 import android.os.UserHandle;
 
@@ -75,7 +74,7 @@
         mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
         mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
 
-        mUserId = builder.mUserId;
+        mUserId = Objects.requireNonNull(builder.mUserId, "userId must be set");
         mUserConfigAllowed = builder.mUserConfigAllowed;
         mLocationEnabledSetting = builder.mLocationEnabledSetting;
         mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
@@ -151,8 +150,7 @@
      * Returns true if the user is allowed to modify time zone configuration, e.g. can be false due
      * to device policy (enterprise).
      *
-     * <p>See also {@link #createCapabilitiesAndConfig(boolean)} for situations where this value
-     * are ignored.
+     * <p>See also {@link #asCapabilities(boolean)} for situations where this value is ignored.
      */
     public boolean isUserConfigAllowed() {
         return mUserConfigAllowed;
@@ -196,20 +194,8 @@
                 || getGeoDetectionRunInBackgroundEnabled());
     }
 
-    /**
-     * Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values.
-     *
-     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
-     *   policy restrictions that should apply to actual users can be ignored
-     */
-    public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
-            boolean bypassUserPolicyChecks) {
-        return new TimeZoneCapabilitiesAndConfig(
-                asCapabilities(bypassUserPolicyChecks), asConfiguration());
-    }
-
     @NonNull
-    private TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
+    public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
         UserHandle userHandle = UserHandle.of(mUserId);
         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
 
@@ -262,7 +248,7 @@
     }
 
     /** Returns a {@link TimeZoneConfiguration} from the configuration values. */
-    private TimeZoneConfiguration asConfiguration() {
+    public TimeZoneConfiguration asConfiguration() {
         return new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
                 .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
@@ -335,8 +321,7 @@
      */
     public static class Builder {
 
-        private final @UserIdInt int mUserId;
-
+        private @UserIdInt Integer mUserId;
         private boolean mUserConfigAllowed;
         private boolean mTelephonyDetectionSupported;
         private boolean mGeoDetectionSupported;
@@ -348,11 +333,9 @@
         private boolean mGeoDetectionEnabledSetting;
 
         /**
-         * Creates a new Builder with only the userId set.
+         * Creates a new Builder.
          */
-        public Builder(@UserIdInt int userId) {
-            mUserId = userId;
-        }
+        public Builder() {}
 
         /**
          * Creates a new Builder by copying values from an existing instance.
@@ -371,6 +354,14 @@
         }
 
         /**
+         * Sets the user ID the configuration is for.
+         */
+        public Builder setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        /**
          * Sets whether the user is allowed to configure time zone settings on this device.
          */
         public Builder setUserConfigAllowed(boolean configAllowed) {
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 4749f73..5cb48c2 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -18,8 +18,6 @@
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 
@@ -29,7 +27,6 @@
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
 
 import java.io.PrintWriter;
-import java.util.Objects;
 
 /**
  * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
@@ -38,29 +35,7 @@
 
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
-    @NonNull private final Context mContext;
-    @NonNull private final Handler mHandler;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
-
-    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
-        mContext = Objects.requireNonNull(context);
-        mHandler = Objects.requireNonNull(handler);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
-    }
-
-    @Override
-    public void setConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
-        ConfigurationChangeListener configurationChangeListener =
-                () -> mHandler.post(listener::onChange);
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
-    }
-
-    @Override
-    @NonNull
-    public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+    EnvironmentImpl() {
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 8da5d6a..4ac2ba5 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -59,20 +59,18 @@
      * Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
      * The listener is invoked on the main thread.
      */
-    void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener);
+    void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Removes a listener previously added via {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+     * #addConfigurationInternalChangeListener(StateChangeListener)}.
      */
-    void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener);
+    void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
      * snapshot so callers must use {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+     * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
      * changes.
      */
     @NonNull
@@ -104,8 +102,7 @@
      *
      * <p>Note: Currently only for use by long-lived objects; there is no associated remove method.
      */
-    void addLocationTimeZoneManagerConfigListener(
-            @NonNull ConfigurationChangeListener listener);
+    void addLocationTimeZoneManagerConfigListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns {@code true} if the telephony-based time zone detection feature is supported on the
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index e2f4246..dfb9619 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -22,7 +22,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -104,8 +103,8 @@
     @NonNull private final LocationManager mLocationManager;
 
     @GuardedBy("this")
-    @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
-            new ArrayList<>();
+    @NonNull
+    private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
 
     /**
      * The mode to use for the primary location time zone provider in a test. Setting this
@@ -207,20 +206,20 @@
     }
 
     private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+        for (StateChangeListener changeListener : mConfigurationInternalListeners) {
             changeListener.onChange();
         }
     }
 
     @Override
     public synchronized void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
     }
 
     @Override
     public synchronized void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
     }
 
@@ -237,10 +236,10 @@
             @NonNull TimeZoneConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) {
         Objects.requireNonNull(requestedConfiguration);
 
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId)
-                .createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
-        TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
+        ConfigurationInternal configurationInternal = getConfigurationInternal(userId);
+        TimeZoneCapabilities capabilities =
+                configurationInternal.asCapabilities(bypassUserPolicyChecks);
+        TimeZoneConfiguration oldConfiguration = configurationInternal.asConfiguration();
 
         final TimeZoneConfiguration newConfiguration =
                 capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
@@ -292,7 +291,8 @@
     @Override
     @NonNull
     public synchronized ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
-        return new ConfigurationInternal.Builder(userId)
+        return new ConfigurationInternal.Builder()
+                .setUserId(userId)
                 .setTelephonyDetectionFeatureSupported(
                         isTelephonyTimeZoneDetectionFeatureSupported())
                 .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
@@ -354,7 +354,7 @@
 
     @Override
     public void addLocationTimeZoneManagerConfigListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mServerFlags.addListener(listener, LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH);
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
similarity index 78%
rename from services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
rename to services/core/java/com/android/server/timezonedetector/StateChangeListener.java
index aa8ad37..2b5639c 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
+++ b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
@@ -17,11 +17,11 @@
 package com.android.server.timezonedetector;
 
 /**
- * A listener used to receive notification that configuration has / may have changed (depending on
+ * A listener used to receive notification that state has / may have changed (depending on
  * the usecase).
  */
 @FunctionalInterface
-public interface ConfigurationChangeListener {
-    /** Called when the configuration may have changed. */
+public interface StateChangeListener {
+    /** Called when something (may have) changed. */
     void onChange();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index ce64eac..dfb44df 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -35,17 +35,14 @@
     @NonNull private final Context mContext;
     @NonNull private final Handler mHandler;
     @NonNull private final CurrentUserIdentityInjector mCurrentUserIdentityInjector;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
     public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
             @NonNull CurrentUserIdentityInjector currentUserIdentityInjector,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mCurrentUserIdentityInjector = Objects.requireNonNull(currentUserIdentityInjector);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
     }
 
@@ -53,10 +50,9 @@
     @NonNull
     public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfigForDpm() {
         int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
-        ConfigurationInternal configurationInternal =
-                mServiceConfigAccessor.getConfigurationInternal(currentUserId);
         final boolean bypassUserPolicyChecks = true;
-        return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+        return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                currentUserId, bypassUserPolicyChecks);
     }
 
     @Override
@@ -65,7 +61,7 @@
 
         int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
         final boolean bypassUserPolicyChecks = true;
-        return mServiceConfigAccessor.updateConfiguration(
+        return mTimeZoneDetectorStrategy.updateConfiguration(
                 currentUserId, configuration, bypassUserPolicyChecks);
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 13f1694..f415cf0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -83,7 +83,7 @@
             ServiceConfigAccessor serviceConfigAccessor =
                     ServiceConfigAccessorImpl.getInstance(context);
             TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                    TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
+                    TimeZoneDetectorStrategyImpl.create(handler, serviceConfigAccessor);
             DeviceActivityMonitor deviceActivityMonitor =
                     DeviceActivityMonitorImpl.create(context, handler);
 
@@ -99,16 +99,14 @@
             CurrentUserIdentityInjector currentUserIdentityInjector =
                     CurrentUserIdentityInjector.REAL;
             TimeZoneDetectorInternal internal = new TimeZoneDetectorInternalImpl(
-                    context, handler, currentUserIdentityInjector, serviceConfigAccessor,
-                    timeZoneDetectorStrategy);
+                    context, handler, currentUserIdentityInjector, timeZoneDetectorStrategy);
             publishLocalService(TimeZoneDetectorInternal.class, internal);
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
             CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
             TimeZoneDetectorService service = new TimeZoneDetectorService(
-                    context, handler, callerIdentityInjector, serviceConfigAccessor,
-                    timeZoneDetectorStrategy);
+                    context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
 
             // Dump the device activity monitor when the service is dumped.
             service.addDumpable(deviceActivityMonitor);
@@ -127,9 +125,6 @@
     private final CallerIdentityInjector mCallerIdentityInjector;
 
     @NonNull
-    private final ServiceConfigAccessor mServiceConfigAccessor;
-
-    @NonNull
     private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
     /**
@@ -150,18 +145,16 @@
     @VisibleForTesting
     public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
             @NonNull CallerIdentityInjector callerIdentityInjector,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
 
         // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
-        // the configuration changes for any reason.
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(
-                () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread));
+        // the detector state changes for any reason.
+        mTimeZoneDetectorStrategy.addChangeListener(
+                () -> mHandler.post(this::handleChangeOnHandlerThread));
     }
 
     @Override
@@ -174,12 +167,15 @@
     TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId) {
         enforceManageTimeZoneDetectorPermission();
 
+        // Resolve constants like USER_CURRENT to the true user ID as needed.
+        int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, false, "getCapabilitiesAndConfig", null);
+
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            ConfigurationInternal configurationInternal =
-                    mServiceConfigAccessor.getConfigurationInternal(userId);
             final boolean bypassUserPolicyChecks = false;
-            return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+            return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                    resolvedUserId, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
@@ -204,7 +200,7 @@
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             final boolean bypassUserPolicyChecks = false;
-            return mServiceConfigAccessor.updateConfiguration(
+            return mTimeZoneDetectorStrategy.updateConfiguration(
                     resolvedUserId, configuration, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
@@ -285,8 +281,9 @@
         }
     }
 
-    void handleConfigurationInternalChangedOnHandlerThread() {
-        // Configuration has changed, but each user may have a different view of the configuration.
+    void handleChangeOnHandlerThread() {
+        // Detector state has changed. Each user may have a different view of the configuration so
+        // no information is passed; each client must query what they're interested in.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
         synchronized (mListeners) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 69284e3..328cf72 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,6 +17,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -94,6 +96,48 @@
  */
 public interface TimeZoneDetectorStrategy extends Dumpable {
 
+    /**
+     * Adds a listener that will be triggered when something changes that could affect the result
+     * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This
+     * includes the current user changing. This is exposed so that (indirect) users like SettingsUI
+     * can monitor for changes to data derived from {@link TimeZoneCapabilitiesAndConfig} and update
+     * the UI accordingly.
+     */
+    void addChangeListener(StateChangeListener listener);
+
+    /**
+     * Returns a {@link TimeZoneCapabilitiesAndConfig} object for the specified user.
+     *
+     * <p>The strategy is dependent on device state like current user, settings and device config.
+     * These updates are usually handled asynchronously, so callers should expect some delay between
+     * a change being made directly to services like settings and the strategy becoming aware of
+     * them. Changes made via {@link #updateConfiguration} will be visible immediately.
+     *
+     * @param userId the user ID to retrieve the information for
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+            @UserIdInt int userId, boolean bypassUserPolicyChecks);
+
+    /**
+     * Updates the configuration properties that control a device's time zone behavior.
+     *
+     * <p>This method returns {@code true} if the configuration was changed, {@code false}
+     * otherwise.
+     *
+     * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to
+     * subsequent calls.
+     *
+     * @param userId the current user ID, supplied to make sure that the asynchronous process
+     *   that happens when users switch is completed when the call is made
+     * @param configuration the configuration changes
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    boolean updateConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration,
+            boolean bypassUserPolicyChecks);
+
     /** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
     @NonNull
     TimeZoneState getTimeZoneState();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 18c8885..ecf25e9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -31,10 +31,10 @@
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilities;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.content.Context;
 import android.os.Handler;
 import android.os.TimestampedValue;
 import android.util.IndentingPrintWriter;
@@ -46,6 +46,7 @@
 
 import java.io.PrintWriter;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -68,16 +69,6 @@
     public interface Environment {
 
         /**
-         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
-         * changes that could affect the content of {@link ConfigurationInternal}.
-         * This is invoked during system server setup.
-         */
-        void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
-
-        /** Returns the {@link ConfigurationInternal} for the current user. */
-        @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
-
-        /**
          * Returns the device's currently configured time zone. May return an empty string.
          */
         @NonNull String getDeviceTimeZone();
@@ -206,6 +197,22 @@
     private final ReferenceWithHistory<ManualTimeZoneSuggestion> mLatestManualSuggestion =
             new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
+    @NonNull
+    private final ServiceConfigAccessor mServiceConfigAccessor;
+
+    /** The handler used for asynchronous operations triggered by this. */
+    @NonNull
+    private final Handler mStateChangeHandler;
+
+    @GuardedBy("this")
+    @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+
+    /**
+     * A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
+     * because it is relatively heavyweight to obtain and is used more often than it is expected to
+     * change. Because many operations are asynchronous, this value may be out of date but should
+     * be "eventually consistent".
+     */
     @GuardedBy("this")
     @NonNull
     private ConfigurationInternal mCurrentConfigurationInternal;
@@ -229,29 +236,93 @@
      * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
      */
     public static TimeZoneDetectorStrategyImpl create(
-            @NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+            @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) {
 
-        Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor);
-        return new TimeZoneDetectorStrategyImpl(environment);
+        Environment environment = new EnvironmentImpl();
+        return new TimeZoneDetectorStrategyImpl(serviceConfigAccessor, handler, environment);
     }
 
     @VisibleForTesting
-    public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
+    public TimeZoneDetectorStrategyImpl(
+            @NonNull ServiceConfigAccessor serviceConfigAccessor,
+            @NonNull Handler handler, @NonNull Environment environment) {
         mEnvironment = Objects.requireNonNull(environment);
+        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
+        mStateChangeHandler = Objects.requireNonNull(handler);
 
         // Start with telephony fallback enabled.
         mTelephonyTimeZoneFallbackEnabled =
                 new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true);
 
         synchronized (this) {
-            mEnvironment.setConfigurationInternalChangeListener(
-                    this::handleConfigurationInternalChanged);
-            mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal();
+            // Listen for config and user changes and get an initial snapshot of configuration.
+            StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
+            mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
+            mCurrentConfigurationInternal =
+                    mServiceConfigAccessor.getCurrentUserConfigurationInternal();
         }
     }
 
     @Override
+    public synchronized TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+            @UserIdInt int userId, boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal;
+        if (mCurrentConfigurationInternal.getUserId() == userId) {
+            // Use the cached snapshot we have.
+            configurationInternal = mCurrentConfigurationInternal;
+        } else {
+            // This is not a common case: It would be unusual to want the configuration for a user
+            // other than the "current" user, but it is supported because it is trivial to do so.
+            // Unlike the current user config, there's no cached copy to worry about so read it
+            // directly from mServiceConfigAccessor.
+            configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
+        }
+        return new TimeZoneCapabilitiesAndConfig(
+                configurationInternal.asCapabilities(bypassUserPolicyChecks),
+                configurationInternal.asConfiguration());
+    }
+
+    @Override
+    public synchronized boolean updateConfiguration(
+            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration,
+            boolean bypassUserPolicyChecks) {
+
+        // Write-through
+        boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration(
+                userId, configuration, bypassUserPolicyChecks);
+
+        // The update above will trigger config update listeners asynchronously if they are needed,
+        // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
+        // wouldn't see the update. So, handle the cache update and notifications here. When the
+        // async update listener triggers it will find everything already up to date and do nothing.
+        if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
+            ConfigurationInternal configurationInternal =
+                    mServiceConfigAccessor.getConfigurationInternal(userId);
+
+            // If the configuration actually changed, update the cached copy synchronously and do
+            // other necessary house-keeping / (async) listener notifications.
+            if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
+                mCurrentConfigurationInternal = configurationInternal;
+
+                String logMsg = "updateConfiguration:"
+                        + " userId=" + userId
+                        + ", configuration=" + configuration
+                        + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
+                        + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
+                logTimeZoneDebugInfo(logMsg);
+
+                handleConfigurationInternalChanged(logMsg);
+            }
+        }
+        return updateSuccessful;
+    }
+
+    @Override
+    public synchronized void addChangeListener(StateChangeListener listener) {
+        mStateChangeListeners.add(listener);
+    }
+
+    @Override
     public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
         Objects.requireNonNull(timeZoneId);
 
@@ -334,9 +405,8 @@
         String timeZoneId = suggestion.getZoneId();
         String cause = "Manual time suggestion received: suggestion=" + suggestion;
 
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+        TimeZoneCapabilities capabilities =
+                currentUserConfig.asCapabilities(bypassUserPolicyChecks);
         if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
             Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
                     + ": capabilities=" + capabilities
@@ -735,18 +805,37 @@
         return findBestTelephonySuggestion();
     }
 
-    private synchronized void handleConfigurationInternalChanged() {
+    /**
+     * Handles a configuration change notification.
+     */
+    private synchronized void handleConfigurationInternalMaybeChanged() {
         ConfigurationInternal currentUserConfig =
-                mEnvironment.getCurrentUserConfigurationInternal();
-        String logMsg = "handleConfigurationInternalChanged:"
-                + " oldConfiguration=" + mCurrentConfigurationInternal
-                + ", newConfiguration=" + currentUserConfig;
-        logTimeZoneDebugInfo(logMsg);
-        mCurrentConfigurationInternal = currentUserConfig;
+                mServiceConfigAccessor.getCurrentUserConfigurationInternal();
 
-        // The configuration change may have changed available suggestions or the way suggestions
-        // are used, so re-run detection.
-        doAutoTimeZoneDetection(currentUserConfig, logMsg);
+        // The configuration may not actually have changed so check before doing anything.
+        if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
+            String logMsg = "handleConfigurationInternalMaybeChanged:"
+                    + " oldConfiguration=" + mCurrentConfigurationInternal
+                    + ", newConfiguration=" + currentUserConfig;
+            logTimeZoneDebugInfo(logMsg);
+
+            mCurrentConfigurationInternal = currentUserConfig;
+
+            handleConfigurationInternalChanged(logMsg);
+        }
+    }
+
+    /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
+    @GuardedBy("this")
+    private void handleConfigurationInternalChanged(@NonNull String logMsg) {
+        // Notify change listeners asynchronously.
+        for (StateChangeListener listener : mStateChangeListeners) {
+            mStateChangeHandler.post(listener::onChange);
+        }
+
+        // The configuration change may have changed available suggestions or the way
+        // suggestions are used, so re-run detection.
+        doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
     }
 
     /**
@@ -760,8 +849,7 @@
         ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
         final boolean bypassUserPolicyChecks = false;
         ipw.println("[Capabilities="
-                + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks)
-                + "]");
+                + mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
         ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
         ipw.println("mEnvironment.getDeviceTimeZoneConfidence()="
                 + mEnvironment.getDeviceTimeZoneConfidence());
@@ -824,6 +912,11 @@
         return mTelephonyTimeZoneFallbackEnabled.getValue();
     }
 
+    @VisibleForTesting
+    public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
+        return mCurrentConfigurationInternal;
+    }
+
     /**
      * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
      */
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
index e7d16c8..5eeafc1 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
@@ -20,9 +20,9 @@
 import android.annotation.NonNull;
 import android.os.SystemClock;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.time.Duration;
 import java.util.Objects;
@@ -35,7 +35,7 @@
         extends LocationTimeZoneProviderController.Environment {
 
     @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
-    @NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener;
+    @NonNull private final StateChangeListener mConfigurationInternalChangeListener;
 
     LocationTimeZoneProviderControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
             @NonNull ServiceConfigAccessor serviceConfigAccessor,
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index cd0096b..c192057 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1752,6 +1752,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.TRUST_LISTENER)
         @Override
         public boolean isTrustUsuallyManaged(int userId) {
+            super.isTrustUsuallyManaged_enforcePermission();
+
             return isTrustUsuallyManagedInternal(userId);
         }
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6a35533..7dc4f97 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1549,8 +1549,9 @@
                     try {
                         mReply.sendResult(null);
                     } catch (RemoteException e) {
-                        Binder.restoreCallingIdentity(ident);
                         Slog.d(TAG, "failed to send callback!", e);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
                     }
                     mReply = null;
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 17a9a63..a9d8d79 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -812,7 +812,6 @@
     StartingData mStartingData;
     WindowState mStartingWindow;
     StartingSurfaceController.StartingSurface mStartingSurface;
-    boolean startingDisplayed;
     boolean startingMoved;
 
     /** The last set {@link DropInputMode} for this activity surface. */
@@ -821,13 +820,6 @@
     /** Whether the input to this activity will be dropped during the current playing animation. */
     private boolean mIsInputDroppedForAnimation;
 
-    /**
-     * If it is non-null, it requires all activities who have the same starting data to be drawn
-     * to remove the starting window.
-     * TODO(b/189385912): Remove starting window related fields after migrating them to task.
-     */
-    private StartingData mSharedStartingData;
-
     boolean mHandleExitSplashScreen;
     @TransferSplashScreenState
     int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -1201,14 +1193,11 @@
             pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
             pw.print(" mIsExiting="); pw.println(mIsExiting);
         }
-        if (mSharedStartingData != null) {
-            pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
-        }
-        if (mStartingWindow != null || mStartingSurface != null
-                || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
+        if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
+                || startingMoved || mVisibleSetFromTransferredStartingWindow) {
             pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
             pw.print(" startingSurface="); pw.print(mStartingSurface);
-            pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+            pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed());
             pw.print(" startingMoved="); pw.print(startingMoved);
             pw.println(" mVisibleSetFromTransferredStartingWindow="
                     + mVisibleSetFromTransferredStartingWindow);
@@ -2687,13 +2676,23 @@
         }
     }
 
+    boolean isStartingWindowDisplayed() {
+        final StartingData data = mStartingData != null ? mStartingData : task != null
+                ? task.mSharedStartingData : null;
+        return data != null && data.mIsDisplayed;
+    }
+
     /** Called when the starting window is added to this activity. */
     void attachStartingWindow(@NonNull WindowState startingWindow) {
         startingWindow.mStartingData = mStartingData;
         mStartingWindow = startingWindow;
-        // The snapshot type may have called associateStartingDataWithTask().
-        if (mStartingData != null && mStartingData.mAssociatedTask != null) {
-            attachStartingSurfaceToAssociatedTask();
+        if (mStartingData != null) {
+            if (mStartingData.mAssociatedTask != null) {
+                // The snapshot type may have called associateStartingDataWithTask().
+                attachStartingSurfaceToAssociatedTask();
+            } else if (isEmbedded()) {
+                associateStartingWindowWithTaskIfNeeded();
+            }
         }
     }
 
@@ -2708,11 +2707,7 @@
     /** Called when the starting window is not added yet but its data is known to fill the task. */
     private void associateStartingDataWithTask() {
         mStartingData.mAssociatedTask = task;
-        task.forAllActivities(r -> {
-            if (r.mVisibleRequested && !r.firstWindowDrawn) {
-                r.mSharedStartingData = mStartingData;
-            }
-        });
+        task.mSharedStartingData = mStartingData;
     }
 
     /** Associates and attaches an added starting window to the current task. */
@@ -2743,10 +2738,8 @@
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
         mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
-        if (mSharedStartingData != null) {
-            mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
-                r.mSharedStartingData = null;
-            });
+        if (task != null) {
+            task.mSharedStartingData = null;
         }
         if (mStartingWindow == null) {
             if (mStartingData != null) {
@@ -2774,7 +2767,6 @@
             mStartingData = null;
             mStartingSurface = null;
             mStartingWindow = null;
-            startingDisplayed = false;
             if (surface == null) {
                 ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
                         + "startingSurface==null, couldn't remove");
@@ -4247,7 +4239,7 @@
      * @return {@code true} if starting window is in app's hierarchy.
      */
     boolean hasStartingWindow() {
-        if (startingDisplayed || mStartingData != null) {
+        if (mStartingData != null) {
             return true;
         }
         for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -4345,10 +4337,7 @@
 
                 // Transfer the starting window over to the new token.
                 mStartingData = fromActivity.mStartingData;
-                mSharedStartingData = fromActivity.mSharedStartingData;
                 mStartingSurface = fromActivity.mStartingSurface;
-                startingDisplayed = fromActivity.startingDisplayed;
-                fromActivity.startingDisplayed = false;
                 mStartingWindow = tStartingWindow;
                 reportedVisible = fromActivity.reportedVisible;
                 fromActivity.mStartingData = null;
@@ -4414,7 +4403,6 @@
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                     "Moving pending starting from %s to %s", fromActivity, this);
             mStartingData = fromActivity.mStartingData;
-            mSharedStartingData = fromActivity.mSharedStartingData;
             fromActivity.mStartingData = null;
             fromActivity.startingMoved = true;
             scheduleAddStartingWindow();
@@ -6526,14 +6514,11 @@
         // Remove starting window directly if is in a pure task. Otherwise if it is associated with
         // a task (e.g. nested task fragment), then remove only if all visible windows in the task
         // are drawn.
-        final Task associatedTask =
-                mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+        final Task associatedTask = task.mSharedStartingData != null ? task : null;
         if (associatedTask == null) {
             removeStartingWindow();
-        } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
-                // Don't block starting window removal if an Activity can't be a starting window
-                // target.
-                && r.mSharedStartingData != null) == null) {
+        } else if (associatedTask.getActivity(
+                r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
             // The last drawn activity may not be the one that owns the starting window.
             final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
             if (r != null) {
@@ -6748,7 +6733,6 @@
         if (mLastTransactionSequence != mWmService.mTransactionSequence) {
             mLastTransactionSequence = mWmService.mTransactionSequence;
             mNumDrawnWindows = 0;
-            startingDisplayed = false;
 
             // There is the main base application window, even if it is exiting, wait for it
             mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6792,9 +6776,9 @@
                         isInterestingAndDrawn = true;
                     }
                 }
-            } else if (w.isDrawn()) {
+            } else if (mStartingData != null && w.isDrawn()) {
                 // The starting window for this container is drawn.
-                startingDisplayed = true;
+                mStartingData.mIsDisplayed = true;
             }
         }
 
@@ -7552,7 +7536,8 @@
 
         ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
                 + ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
-                this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
+                this, reportedVisible, okToDisplay(), okToAnimate(),
+                isStartingWindowDisplayed());
 
         // clean up thumbnail window
         if (mThumbnail != null) {
@@ -9650,7 +9635,7 @@
         if (mStartingWindow != null) {
             mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW);
         }
-        proto.write(STARTING_DISPLAYED, startingDisplayed);
+        proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed());
         proto.write(STARTING_MOVED, startingMoved);
         proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
                 mVisibleSetFromTransferredStartingWindow);
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index d42a74f..7c0d658 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -40,6 +40,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.OptionalInt;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -332,9 +333,10 @@
             String criticalEvents =
                     CriticalEventLog.getInstance().logLinesForSystemServerTraceFile();
             final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
-                    null /* processCpuTracker */, null /* lastPids */, nativePids,
+                    null /* processCpuTracker */, null /* lastPids */,
+                    CompletableFuture.completedFuture(nativePids),
                     null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents,
-                    null/* AnrLatencyTracker */);
+                    Runnable::run, null/* AnrLatencyTracker */);
             if (tracesFile != null) {
                 tracesFile.renameTo(
                         new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index c2e87e6..d1bda22 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1203,11 +1203,11 @@
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
                             + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
-                    activity, activity.allDrawn, activity.startingDisplayed,
+                    activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
                     activity.startingMoved, activity.isRelaunching(),
                     activity.mStartingWindow);
             final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
-            if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
+            if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
                 return false;
             }
             if (allDrawn) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1cb83f12..d1d39af 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -243,16 +243,22 @@
             } else if (currentActivity.isRootOfTask()) {
                 // TODO(208789724): Create single source of truth for this, maybe in
                 //  RootWindowContainer
-                // TODO: Also check Task.shouldUpRecreateTaskLocked() for prevActivity logic
                 prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask);
                 removedWindowContainer = currentTask;
-                prevActivity = prevTask.getTopNonFinishingActivity();
-                if (prevTask.isActivityTypeHome()) {
-                    backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                // If it reaches the top activity, we will check the below task from parent.
+                // If it's null or multi-window, fallback the type to TYPE_CALLBACK.
+                // or set the type to proper value when it's return to home or another task.
+                if (prevTask == null || prevTask.inMultiWindowMode()) {
+                    backType = BackNavigationInfo.TYPE_CALLBACK;
                 } else {
-                    backType = BackNavigationInfo.TYPE_CROSS_TASK;
+                    prevActivity = prevTask.getTopNonFinishingActivity();
+                    if (prevTask.isActivityTypeHome()) {
+                        backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                        mShowWallpaper = true;
+                    } else {
+                        backType = BackNavigationInfo.TYPE_CROSS_TASK;
+                    }
                 }
-                mShowWallpaper = true;
             }
             infoBuilder.setType(backType);
 
@@ -263,8 +269,10 @@
                     removedWindowContainer,
                     BackNavigationInfo.typeToString(backType));
 
-            // For now, we only animate when going home.
-            boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+            // For now, we only animate when going home and cross task.
+            boolean prepareAnimation =
+                    (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+                            || backType == BackNavigationInfo.TYPE_CROSS_TASK)
                     && adapter != null;
 
             // Only prepare animation if no leash has been created (no animation is running).
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index e7ab63e..13a1cb6 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -216,14 +216,10 @@
             return;
         }
 
-        if (container != null) {
-            // The dim method is called from WindowState.prepareSurfaces(), which is always called
-            // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
-            // relative to the highest Z layer with a dim.
-            t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
-        } else {
-            t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
-        }
+        // The dim method is called from WindowState.prepareSurfaces(), which is always called
+        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+        // relative to the highest Z layer with a dim.
+        t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
         t.setAlpha(d.mDimLayer, alpha);
         t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
 
@@ -231,32 +227,6 @@
     }
 
     /**
-     * Finish a dim started by dimAbove in the case there was no call to dimAbove.
-     *
-     * @param t A Transaction in which to finish the dim.
-     */
-    void stopDim(SurfaceControl.Transaction t) {
-        if (mDimState != null) {
-            t.hide(mDimState.mDimLayer);
-            mDimState.isVisible = false;
-            mDimState.mDontReset = false;
-        }
-    }
-
-    /**
-     * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
-     * remove this effect. If the Dim can be assosciated with a particular child of the host
-     * consider using the other variant of dimAbove which ties the Dim lifetime to the child
-     * lifetime more explicitly.
-     *
-     * @param t     A transaction in which to apply the Dim.
-     * @param alpha The alpha at which to Dim.
-     */
-    void dimAbove(SurfaceControl.Transaction t, float alpha) {
-        dim(t, null, 1, alpha, 0);
-    }
-
-    /**
      * Place a dim above the given container, which should be a child of the host container.
      * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
      * and the child should call dimAbove again to request the Dim to continue.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8723994..7bcea36 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -75,11 +75,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -119,11 +115,9 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowLayout;
@@ -318,7 +312,6 @@
     private int mLastAppearance;
     private int mLastBehavior;
     private int mLastRequestedVisibleTypes = Type.defaultVisible();
-    private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
     private AppearanceRegion[] mLastStatusBarAppearanceRegions;
     private LetterboxDetails[] mLastLetterboxDetails;
 
@@ -1341,90 +1334,6 @@
      */
     int selectAnimation(WindowState win, int transit) {
         ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit);
-        if (win == mStatusBar) {
-            if (transit == TRANSIT_EXIT
-                    || transit == TRANSIT_HIDE) {
-                return R.anim.dock_top_exit;
-            } else if (transit == TRANSIT_ENTER
-                    || transit == TRANSIT_SHOW) {
-                return R.anim.dock_top_enter;
-            }
-        } else if (win == mNavigationBar) {
-            if (win.getAttrs().windowAnimations != 0) {
-                return ANIMATION_STYLEABLE;
-            }
-            // This can be on either the bottom or the right or the left.
-            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
-                if (transit == TRANSIT_EXIT
-                        || transit == TRANSIT_HIDE) {
-                    if (mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
-                        return R.anim.dock_bottom_exit_keyguard;
-                    } else {
-                        return R.anim.dock_bottom_exit;
-                    }
-                } else if (transit == TRANSIT_ENTER
-                        || transit == TRANSIT_SHOW) {
-                    return R.anim.dock_bottom_enter;
-                }
-            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
-                if (transit == TRANSIT_EXIT
-                        || transit == TRANSIT_HIDE) {
-                    return R.anim.dock_right_exit;
-                } else if (transit == TRANSIT_ENTER
-                        || transit == TRANSIT_SHOW) {
-                    return R.anim.dock_right_enter;
-                }
-            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
-                if (transit == TRANSIT_EXIT
-                        || transit == TRANSIT_HIDE) {
-                    return R.anim.dock_left_exit;
-                } else if (transit == TRANSIT_ENTER
-                        || transit == TRANSIT_SHOW) {
-                    return R.anim.dock_left_enter;
-                }
-            }
-        } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
-                || win == mExtraNavBarAlt) {
-            if (win.getAttrs().windowAnimations != 0) {
-                return ANIMATION_STYLEABLE;
-            }
-
-            int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition;
-
-            boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE;
-            boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW;
-
-            switch (pos) {
-                case ALT_BAR_LEFT:
-                    if (isExitOrHide) {
-                        return R.anim.dock_left_exit;
-                    } else if (isEnterOrShow) {
-                        return R.anim.dock_left_enter;
-                    }
-                    break;
-                case ALT_BAR_RIGHT:
-                    if (isExitOrHide) {
-                        return R.anim.dock_right_exit;
-                    } else if (isEnterOrShow) {
-                        return R.anim.dock_right_enter;
-                    }
-                    break;
-                case ALT_BAR_BOTTOM:
-                    if (isExitOrHide) {
-                        return R.anim.dock_bottom_exit;
-                    } else if (isEnterOrShow) {
-                        return R.anim.dock_bottom_enter;
-                    }
-                    break;
-                case ALT_BAR_TOP:
-                    if (isExitOrHide) {
-                        return R.anim.dock_top_exit;
-                    } else if (isEnterOrShow) {
-                        return R.anim.dock_top_enter;
-                    }
-                    break;
-            }
-        }
 
         if (transit == TRANSIT_PREVIEW_DONE) {
             if (win.hasAppShownWindows()) {
@@ -2246,35 +2155,16 @@
             mService.mInputManager.setSystemUiLightsOut(
                     isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
         }
-        final InsetsVisibilities requestedVisibilities =
-                mLastRequestedVisibleTypes == requestedVisibleTypes
-                        ? mRequestedVisibilities
-                        : toInsetsVisibilities(requestedVisibleTypes);
         mLastAppearance = appearance;
         mLastBehavior = behavior;
         mLastRequestedVisibleTypes = requestedVisibleTypes;
-        mRequestedVisibilities = requestedVisibilities;
         mFocusedApp = focusedApp;
         mLastFocusIsFullscreen = isFullscreen;
         mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
         mLastLetterboxDetails = letterboxDetails;
         callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
                 appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
-                requestedVisibilities, focusedApp, letterboxDetails));
-    }
-
-    // TODO (253420890): Remove this when removing mRequestedVisibilities.
-    private static InsetsVisibilities toInsetsVisibilities(@InsetsType int requestedVisibleTypes) {
-        final @InsetsType int defaultVisibleTypes = WindowInsets.Type.defaultVisible();
-        final InsetsVisibilities insetsVisibilities = new InsetsVisibilities();
-        for (@InternalInsetsType int i = InsetsState.SIZE - 1; i >= 0; i--) {
-            @InsetsType int type = InsetsState.toPublicType(i);
-            if ((type & (requestedVisibleTypes ^ defaultVisibleTypes)) != 0) {
-                // We only set the visibility if it is different from the default one.
-                insetsVisibilities.setVisibility(i, (type & requestedVisibleTypes) != 0);
-            }
-        }
-        return insetsVisibilities;
+                requestedVisibleTypes, focusedApp, letterboxDetails));
     }
 
     private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a8d13c5..eaa08fd 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1578,7 +1578,9 @@
                         false /* forceRelayout */);
             } else {
                 // Revert the rotation to our saved value if we transition from HALF_FOLDED.
-                mRotation = mHalfFoldSavedRotation;
+                if (mHalfFoldSavedRotation != -1) {
+                    mRotation = mHalfFoldSavedRotation;
+                }
                 // Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
                 // so we will override USER_ROTATION_LOCKED and allow a rotation).
                 mService.updateRotation(false /* alwaysSendConfiguration */,
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index f11c2a7..dcb7fe3 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -604,7 +604,10 @@
                 getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
             }
             if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
-                getStatusBarService().showPinningEnterExitToast(false /* entering */);
+                final IStatusBarService statusBarService = getStatusBarService();
+                if (statusBarService != null) {
+                    statusBarService.showPinningEnterExitToast(false /* entering */);
+                }
             }
             mWindowManager.onLockTaskStateChanged(mLockTaskModeState);
         } catch (RemoteException ex) {
@@ -619,7 +622,10 @@
     void showLockTaskToast() {
         if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
             try {
-                getStatusBarService().showPinningEscapeToast();
+                final IStatusBarService statusBarService = getStatusBarService();
+                if (statusBarService != null) {
+                    statusBarService.showPinningEscapeToast();
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to send pinning escape toast", e);
             }
@@ -727,7 +733,10 @@
         // When lock task starts, we disable the status bars.
         try {
             if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
-                getStatusBarService().showPinningEnterExitToast(true /* entering */);
+                final IStatusBarService statusBarService = getStatusBarService();
+                if (statusBarService != null) {
+                    statusBarService.showPinningEnterExitToast(true /* entering */);
+                }
             }
             mWindowManager.onLockTaskStateChanged(lockTaskModeState);
             mLockTaskModeState = lockTaskModeState;
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 9c85bc0..1cc1a57 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -20,13 +20,12 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 
-import java.util.Comparator;
-import java.util.Iterator;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.TreeSet;
 import java.util.function.Consumer;
 
 /**
@@ -39,15 +38,13 @@
     static final int FLAG_CROSS_USERS = 1 << 2;
     static final int FLAG_KEEP_INTENT_EXTRA = 1 << 3;
 
-    // Comparator to sort by last active time (descending)
-    private static final Comparator<Task> LAST_ACTIVE_TIME_COMPARATOR =
-            (o1, o2) -> {
-                return o1.lastActiveTime == o2.lastActiveTime
-                        ? Integer.signum(o2.mTaskId - o1.mTaskId) :
-                        Long.signum(o2.lastActiveTime - o1.lastActiveTime);
-            };
-
-    private final TreeSet<Task> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
+    // Tasks are sorted in order {focusedVisibleTasks, visibleTasks, invisibleTasks}.
+    private final ArrayList<Task> mTmpSortedTasks = new ArrayList<>();
+    // mTmpVisibleTasks, mTmpInvisibleTasks and mTmpFocusedTasks are sorted from top
+    // to bottom.
+    private final ArrayList<Task> mTmpVisibleTasks = new ArrayList<>();
+    private final ArrayList<Task> mTmpInvisibleTasks = new ArrayList<>();
+    private final ArrayList<Task> mTmpFocusedTasks = new ArrayList<>();
 
     private int mCallingUid;
     private int mUserId;
@@ -65,8 +62,6 @@
             return;
         }
 
-        // Gather all of the tasks across all of the tasks, and add them to the sorted set
-        mTmpSortedSet.clear();
         mCallingUid = callingUid;
         mUserId = UserHandle.getUserId(callingUid);
         mCrossUser = (flags & FLAG_CROSS_USERS) == FLAG_CROSS_USERS;
@@ -77,19 +72,64 @@
         mRecentTasks = recentTasks;
         mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA;
 
-        root.forAllLeafTasks(this, false /* traverseTopToBottom */);
+        if (root instanceof RootWindowContainer) {
+            ((RootWindowContainer) root).forAllDisplays(dc -> {
+                final Task focusedTask = dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null;
+                if (focusedTask != null) {
+                    mTmpFocusedTasks.add(focusedTask);
+                }
+                processTaskInWindowContainer(dc);
+            });
+        } else {
+            final DisplayContent dc = root.getDisplayContent();
+            final Task focusedTask = dc != null
+                    ? (dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null)
+                    : null;
+            // May not be include focusedTask if root is DisplayArea.
+            final boolean rootContainsFocusedTask = focusedTask != null
+                    && focusedTask.isDescendantOf(root);
+            if (rootContainsFocusedTask) {
+                mTmpFocusedTasks.add(focusedTask);
+            }
+            processTaskInWindowContainer(root);
+        }
+
+        final int visibleTaskCount = mTmpVisibleTasks.size();
+        for (int i = 0; i < mTmpFocusedTasks.size(); i++) {
+            final Task focusedTask = mTmpFocusedTasks.get(i);
+            final boolean containsFocusedTask = mTmpVisibleTasks.remove(focusedTask);
+            if (containsFocusedTask) {
+                // Put the visible focused task at the first position.
+                mTmpSortedTasks.add(focusedTask);
+            }
+        }
+        if (!mTmpVisibleTasks.isEmpty()) {
+            mTmpSortedTasks.addAll(mTmpVisibleTasks);
+        }
+        if (!mTmpInvisibleTasks.isEmpty()) {
+            mTmpSortedTasks.addAll(mTmpInvisibleTasks);
+        }
 
         // Take the first {@param maxNum} tasks and create running task infos for them
-        final Iterator<Task> iter = mTmpSortedSet.iterator();
-        while (iter.hasNext()) {
-            if (maxNum == 0) {
-                break;
-            }
-
-            final Task task = iter.next();
-            list.add(createRunningTaskInfo(task));
-            maxNum--;
+        final int size = Math.min(maxNum, mTmpSortedTasks.size());
+        final long now = SystemClock.elapsedRealtime();
+        for (int i = 0; i < size; i++) {
+            final Task task = mTmpSortedTasks.get(i);
+            // Override the last active to current time for the visible tasks because the visible
+            // tasks can be considered to be currently active, the values are descending as
+            // the item order.
+            final long visibleActiveTime = i < visibleTaskCount ? now + size - i : -1;
+            list.add(createRunningTaskInfo(task, visibleActiveTime));
         }
+
+        mTmpFocusedTasks.clear();
+        mTmpVisibleTasks.clear();
+        mTmpInvisibleTasks.clear();
+        mTmpSortedTasks.clear();
+    }
+
+    private void processTaskInWindowContainer(WindowContainer wc) {
+        wc.forAllLeafTasks(this, true /* traverseTopToBottom */);
     }
 
     @Override
@@ -117,25 +157,20 @@
             // home & recent tasks
             return;
         }
-
         if (task.isVisible()) {
-            // For the visible task, update the last active time so that it can be used to determine
-            // the order of the tasks (it may not be set for newly created tasks)
-            task.touchActiveTime();
-            if (!task.isFocused()) {
-                // TreeSet doesn't allow the same value and make sure this task is lower than the
-                // focused one.
-                task.lastActiveTime -= mTmpSortedSet.size();
-            }
+            mTmpVisibleTasks.add(task);
+        } else {
+            mTmpInvisibleTasks.add(task);
         }
-
-        mTmpSortedSet.add(task);
     }
 
     /** Constructs a {@link RunningTaskInfo} from a given {@param task}. */
-    private RunningTaskInfo createRunningTaskInfo(Task task) {
+    private RunningTaskInfo createRunningTaskInfo(Task task, long visibleActiveTime) {
         final RunningTaskInfo rti = new RunningTaskInfo();
         task.fillTaskInfo(rti, !mKeepIntentExtra);
+        if (visibleActiveTime > 0) {
+            rti.lastActiveTime = visibleActiveTime;
+        }
         // Fill in some deprecated values
         rti.id = rti.taskId;
 
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index fbee343..300a894 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,9 @@
      */
     Task mAssociatedTask;
 
+    /** Whether the starting window is drawn. */
+    boolean mIsDisplayed;
+
     protected StartingData(WindowManagerService service, int typeParams) {
         mService = service;
         mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 435ab97..d06f271 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -358,6 +358,13 @@
 
     int mLockTaskUid = -1;  // The uid of the application that called startLockTask().
 
+    /**
+     * If non-null, the starting window should cover the associated task. It is assigned when the
+     * parent activity of starting window is put in a partial area of the task. This field will be
+     * cleared when all visible activities in this task are drawn.
+     */
+    StartingData mSharedStartingData;
+
     /** The process that had previously hosted the root activity of this task.
      * Used to know that we should try harder to keep this process around, in case the
      * user wants to return to it. */
@@ -3673,6 +3680,9 @@
         if (mRootProcess != null) {
             pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
         }
+        if (mSharedStartingData != null) {
+            pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+        }
         pw.print(prefix); pw.print("taskId=" + mTaskId);
         pw.println(" rootTaskId=" + getRootTaskId());
         pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 230b760..063f0db 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1891,10 +1891,10 @@
     RemoteAnimationTarget createRemoteAnimationTarget(
             RemoteAnimationController.RemoteAnimationRecord record) {
         final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
-                // There may be a trampoline activity without window on top of the existing task
-                // which is moving to front. Exclude the finishing activity so the window of next
-                // activity can be chosen to create the animation target.
-                ? getTopNonFinishingActivity()
+                // There may be a launching (e.g. trampoline or embedded) activity without a window
+                // on top of the existing task which is moving to front. Exclude finishing activity
+                // so the window of next activity can be chosen to create the animation target.
+                ? getActivity(r -> !r.finishing && r.hasChild())
                 : getTopMostActivity();
         return activity != null ? activity.createRemoteAnimationTarget(record) : null;
     }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3590e9c2..7415376 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1547,6 +1547,12 @@
         return mTransitionController.mTransitionMetricsReporter;
     }
 
+    @Override
+    public IBinder getApplyToken() {
+        enforceTaskPermission("getApplyToken()");
+        return SurfaceControl.Transaction.getDefaultApplyToken();
+    }
+
     /** Whether the configuration changes are important to report back to an organizer. */
     static boolean configurationsAreEqualForOrganizer(
             Configuration newConfig, @Nullable Configuration oldConfig) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f30c435..0168d50 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1838,8 +1838,8 @@
      * @return {@code true} if one or more windows have been displayed, else false.
      */
     boolean hasAppShownWindows() {
-        return mActivityRecord != null
-                && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+        return mActivityRecord != null && (mActivityRecord.firstWindowDrawn
+                || mActivityRecord.isStartingWindowDisplayed());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5c0557f..a0ba8fd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -374,13 +374,6 @@
     }
 
     void destroySurfaceLocked(SurfaceControl.Transaction t) {
-        final ActivityRecord activity = mWin.mActivityRecord;
-        if (activity != null) {
-            if (mWin == activity.mStartingWindow) {
-                activity.startingDisplayed = false;
-            }
-        }
-
         if (mSurfaceController == null) {
             return;
         }
@@ -602,11 +595,17 @@
             return true;
         }
 
-        final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
-        if (isEntrance && isImeWindow) {
+        if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
             mWin.getDisplayContent().adjustForImeIfNeeded();
-            mWin.setDisplayLayoutNeeded();
-            mService.mWindowPlacerLocked.requestTraversal();
+            if (isEntrance) {
+                mWin.setDisplayLayoutNeeded();
+                mService.mWindowPlacerLocked.requestTraversal();
+            }
+        }
+
+        if (mWin.mControllableInsetProvider != null) {
+            // All our animations should be driven by the insets control target.
+            return false;
         }
 
         // Only apply an animation if the display isn't frozen.  If it is
@@ -654,14 +653,10 @@
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                 mAnimationIsEntrance = isEntrance;
             }
-        } else if (!isImeWindow) {
+        } else {
             mWin.cancelAnimation();
         }
 
-        if (!isEntrance && isImeWindow) {
-            mWin.getDisplayContent().adjustForImeIfNeeded();
-        }
-
         return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 40412db..321f022 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
@@ -155,5 +156,14 @@
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
             return cancelTransport;
         }
+
+        @Override
+        public ICancellationSignal clearCredentialSession(
+                IClearCredentialSessionCallback callback, String callingPackage) {
+            // TODO: implement.
+            Log.i(TAG, "clearCredentialSession");
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            return cancelTransport;
+        }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 5b7b8f4..4d92b7f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -25,6 +25,7 @@
 import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;
 import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
 import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
+import static com.android.server.am.BroadcastQueueTest.withPriority;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -46,8 +47,10 @@
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.HandlerThread;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 
 import androidx.test.filters.SmallTest;
 
@@ -59,6 +62,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.List;
@@ -283,7 +288,7 @@
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         queue.setProcessCached(false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -305,12 +310,12 @@
         // enqueue a bg-priority broadcast then a fg-priority one
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
         final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
-        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         // verify that:
         // (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -339,9 +344,9 @@
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
-                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
-                        makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, 1);
+                List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+                        withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1);
 
         assertFalse(queue.isRunnable());
         assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -363,7 +368,7 @@
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         mConstants.MAX_PENDING_BROADCASTS = 128;
         queue.invalidateRunnableAt();
@@ -377,6 +382,55 @@
     }
 
     /**
+     * Confirm that we always prefer running pending items marked as "urgent",
+     * then "normal", then "offload", dispatching by the relative ordering
+     * within each of those clustering groups.
+     */
+    @Test
+    public void testMakeActiveNextPending() {
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
+
+        // To maximize test coverage, dump current state; we're not worried
+        // about the actual output, just that we don't crash
+        queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED);
+        queue.dumpLocked(SystemClock.uptimeMillis(),
+                new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream())));
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_AIRPLANE_MODE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+        assertTrue(queue.isEmpty());
+    }
+
+    /**
      * Verify that sending a broadcast that removes any matching pending
      * broadcasts is applied as expected.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index de59603..fd605f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -503,6 +503,11 @@
         return ai;
     }
 
+    static ResolveInfo withPriority(ResolveInfo info, int priority) {
+        info.priority = priority;
+        return info;
+    }
+
     static ResolveInfo makeManifestReceiver(String packageName, String name) {
         return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
     }
@@ -1634,4 +1639,22 @@
         waitForIdle();
         verify(mAms, never()).enqueueOomAdjTargetLocked(any());
     }
+
+    /**
+     * Verify that expected events are triggered when a broadcast is finished.
+     */
+    @Test
+    public void testNotifyFinished() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        final BroadcastRecord record = makeBroadcastRecord(intent, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)));
+        enqueueBroadcast(record);
+
+        waitForIdle();
+        verify(mAms).notifyBroadcastFinishedLocked(eq(record));
+        verify(mAms).addBroadcastStatLocked(eq(Intent.ACTION_TIMEZONE_CHANGED), eq(PACKAGE_RED),
+                eq(1), eq(0), anyLong());
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 11573c5..05ed0e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,9 +24,10 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.isPrioritized;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -99,6 +100,16 @@
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 0))));
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), -10))));
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
+
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
     }
 
     @Test
@@ -111,6 +122,17 @@
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 10),
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+        assertArrayEquals(new int[] {-1,-1,-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+        assertArrayEquals(new int[] {-1,-1,-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10),
+                        createResolveInfo(PACKAGE2, getAppId(2), 10),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
     }
 
     @Test
@@ -123,6 +145,19 @@
                 createResolveInfo(PACKAGE1, getAppId(1), 0),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+        assertArrayEquals(new int[] {0,1,2},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), -10),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+        assertArrayEquals(new int[] {0,0,2,3,3},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10),
+                        createResolveInfo(PACKAGE3, getAppId(3), 20),
+                        createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
     }
 
     @Test
@@ -543,4 +578,9 @@
     private static int getAppId(int i) {
         return Process.FIRST_APPLICATION_UID + i;
     }
+
+    private static boolean isPrioritized(List<Object> receivers) {
+        return BroadcastRecord.isPrioritized(
+                calculateBlockedUntilTerminalCount(receivers, false), false);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index 0b84a60..e6ab73a 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -49,6 +49,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -62,6 +63,7 @@
     private AnrHelper mAnrHelper;
 
     private ProcessRecord mAnrApp;
+    private ExecutorService mExecutorService;
 
     @Rule
     public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
@@ -88,7 +90,9 @@
                         return mServiceThreadRule.getThread().getThreadHandler();
                     }
                 }, mServiceThreadRule.getThread());
-            mAnrHelper = new AnrHelper(service);
+            mExecutorService = mock(ExecutorService.class);
+
+            mAnrHelper = new AnrHelper(service, mExecutorService);
         });
     }
 
@@ -119,7 +123,7 @@
 
         verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding(
                 eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
-                eq(parentProcess), eq(aboveSystem), eq(timeoutRecord),
+                eq(parentProcess), eq(aboveSystem), eq(timeoutRecord), eq(mExecutorService),
                 eq(false) /* onlyDumpSelf */);
     }
 
@@ -133,7 +137,7 @@
             processingLatch.await();
             return null;
         }).when(mAnrApp.mErrorState).appNotResponding(anyString(), any(), any(), any(),
-                anyBoolean(), any(), anyBoolean());
+                anyBoolean(), any(), any(), anyBoolean());
         final ApplicationInfo appInfo = new ApplicationInfo();
         final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
                 "annotation");
@@ -155,6 +159,7 @@
         processingLatch.countDown();
         // There is only one ANR reported.
         verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS).only()).appNotResponding(
-                anyString(), any(), any(), any(), anyBoolean(), any(), anyBoolean());
+                anyString(), any(), any(), any(), anyBoolean(), any(), eq(mExecutorService),
+                anyBoolean());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
index 70519e4..9cada91 100644
--- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
@@ -45,6 +45,7 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.util.concurrent.ExecutorService;
 
 /**
  * Build/Install/Run:
@@ -58,6 +59,7 @@
 
     private ProcessRecord mProcessRecord;
     private ProcessErrorStateRecord mProcessErrorState;
+    private ExecutorService mExecutorService;
 
     @BeforeClass
     public static void setUpOnce() throws Exception {
@@ -109,6 +111,7 @@
         runWithDexmakerShareClassLoader(() -> {
             mProcessRecord = new ProcessRecord(sService, sContext.getApplicationInfo(),
                     "name", 12345);
+            mExecutorService = mock(ExecutorService.class);
             mProcessErrorState = spy(mProcessRecord.mErrorState);
             doNothing().when(mProcessErrorState).startAppProblemLSP();
             doReturn(false).when(mProcessErrorState).isSilentAnr();
@@ -194,11 +197,11 @@
         assertTrue(mProcessRecord.isKilled());
     }
 
-    private static void appNotResponding(ProcessErrorStateRecord processErrorState,
+    private void appNotResponding(ProcessErrorStateRecord processErrorState,
             String annotation) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(annotation);
         processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
                 null /* parentShortComponentName */, null /* parentProcess */,
-                false /* aboveSystem */, timeoutRecord, false /* onlyDumpSelf */);
+                false /* aboveSystem */, timeoutRecord, mExecutorService, false /* onlyDumpSelf */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index fe92a1d..935d1d8 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -24,7 +24,6 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
 import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.UserHandle.USER_SYSTEM;
 import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -403,7 +402,7 @@
         verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
         continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     @Test
@@ -424,7 +423,7 @@
         verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
         continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     @Test
@@ -531,7 +530,7 @@
         assertFalse(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
                 mUserController.getRunningUsersLU());
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     /**
@@ -964,8 +963,8 @@
         verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
     }
 
-    private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) {
-        verify(mInjector).onUserStarting(userId, visible);
+    private void verifySystemUserVisibilityChangedNotified(boolean visible) {
+        verify(mInjector).notifySystemUserVisibilityChanged(visible);
     }
 
     // Should be public to allow mocking
@@ -1108,6 +1107,11 @@
         void onUserStarting(@UserIdInt int userId, boolean visible) {
             Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
         }
+
+        @Override
+        void notifySystemUserVisibilityChanged(boolean visible) {
+            Log.i(TAG, "notifySystemUserVisibilityChanged(" + visible + ")");
+        }
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 675f0e3..608eeec 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -620,6 +620,20 @@
         verify(mCallback).onClientFinished(any(), eq(true));
     }
 
+    @Test
+    public void sideFpsPowerPressCancelsIsntantly() throws Exception {
+        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+
+        client.onPowerPressed();
+        mLooper.dispatchAll();
+
+        verify(mCallback, never()).onClientFinished(any(), eq(true));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
     private FingerprintAuthenticationClient createClient() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 2b069e3..1fe267f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -57,7 +57,7 @@
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Handler;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
@@ -1081,10 +1081,10 @@
     public void testUdfpsListenerGetsRegistered() {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
-        verify(mStatusBarMock, never()).setUdfpsHbmListener(any());
+        verify(mStatusBarMock, never()).setUdfpsRefreshRateCallback(any());
 
         director.onBootCompleted();
-        verify(mStatusBarMock).setUdfpsHbmListener(eq(director.getUdpfsObserver()));
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(eq(director.getUdpfsObserver()));
     }
 
     @Test
@@ -1093,10 +1093,9 @@
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
         director.start(createMockSensorManager());
         director.onBootCompleted();
-        ArgumentCaptor<IUdfpsHbmListener> captor =
-                ArgumentCaptor.forClass(IUdfpsHbmListener.class);
-        verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
-        IUdfpsHbmListener hbmListener = captor.getValue();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
 
         // Should be no vote initially
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index 3f55f1b..ec61b87 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -20,6 +20,7 @@
 
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.content.pm.UserPackage;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -54,7 +55,7 @@
         addPackage(target(otherTarget), USER);
         addPackage(overlay(OVERLAY, TARGET), USER);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         // The result should be the same for every time
         assertThat(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
@@ -67,7 +68,7 @@
         addPackage(target(TARGET), USER);
         addPackage(overlay(OVERLAY, TARGET), USER);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_DISABLED, 0 /* priority */);
         expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
@@ -101,7 +102,7 @@
         addPackage(overlay(OVERLAY, TARGET), USER);
         configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
         final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -133,7 +134,7 @@
         addPackage(target(TARGET), USER);
         addPackage(overlay(OVERLAY, TARGET), USER);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         final Consumer<ConfigState> setOverlay = (state -> {
             configureSystemOverlay(OVERLAY, state, 0 /* priority */);
@@ -185,7 +186,7 @@
         configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
         configureSystemOverlay(OVERLAY2, ConfigState.MUTABLE_DISABLED, 1 /* priority */);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
         final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -230,7 +231,7 @@
         configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */);
         configureSystemOverlay(OVERLAY2, ConfigState.IMMUTABLE_ENABLED, 1 /* priority */);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
         final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index f69141d..ab52928 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -22,7 +22,6 @@
 import static android.os.OverlayablePolicy.CONFIG_SIGNATURE;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -30,7 +29,7 @@
 
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
-import android.util.Pair;
+import android.content.pm.UserPackage;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -66,7 +65,7 @@
     @Test
     public void testGetOverlayInfo() throws Exception {
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final OverlayManagerServiceImpl impl = getImpl();
         final OverlayInfo oi = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -79,11 +78,11 @@
     @Test
     public void testGetOverlayInfosForTarget() throws Exception {
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY2, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY3, TARGET), USER2,
-                Set.of(new PackageAndUser(OVERLAY3, USER2), new PackageAndUser(TARGET, USER2)));
+                Set.of(UserPackage.of(USER2, OVERLAY3), UserPackage.of(USER2, TARGET)));
 
         final OverlayManagerServiceImpl impl = getImpl();
         final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
@@ -107,13 +106,13 @@
     @Test
     public void testGetOverlayInfosForUser() throws Exception {
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY2, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY3, TARGET2), USER,
-                Set.of(new PackageAndUser(OVERLAY3, USER), new PackageAndUser(TARGET2, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY3), UserPackage.of(USER, TARGET2)));
 
         final OverlayManagerServiceImpl impl = getImpl();
         final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -138,11 +137,11 @@
     @Test
     public void testPriority() throws Exception {
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY2, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY3, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY3, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY3), UserPackage.of(USER, TARGET)));
 
         final OverlayManagerServiceImpl impl = getImpl();
         final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -152,15 +151,15 @@
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
         assertEquals(impl.setLowestPriority(IDENTIFIER3, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+                Optional.of(UserPackage.of(USER, TARGET)));
         assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
 
         assertEquals(impl.setHighestPriority(IDENTIFIER3, USER),
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
         assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+                Optional.of(UserPackage.of(USER, TARGET)));
         assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
     }
 
@@ -170,47 +169,47 @@
         assertNull(impl.getOverlayInfo(IDENTIFIER, USER));
 
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
 
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_DISABLED, IDENTIFIER, USER);
 
         assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         // target upgrades do not change the state of the overlay
         upgradeAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)),
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)),
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         uninstallAndAssert(TARGET, USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
 
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_ENABLED, IDENTIFIER, USER);
     }
 
     @Test
     public void testOnOverlayPackageUpgraded() throws Exception {
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         upgradeAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)),
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)),
+                Set.of(UserPackage.of(USER, TARGET)));
 
         // upgrade to a version where the overlay has changed its target
         upgradeAndAssert(overlay(OVERLAY, TARGET2), USER,
-                Set.of(new PackageAndUser(TARGET, USER)),
-                Set.of(new PackageAndUser(TARGET, USER),
-                        new PackageAndUser(TARGET2, USER)));
+                Set.of(UserPackage.of(USER, TARGET)),
+                Set.of(UserPackage.of(USER, TARGET),
+                        UserPackage.of(USER, TARGET2)));
     }
 
     @Test
@@ -222,10 +221,10 @@
         // request succeeded, and there was a change that needs to be
         // propagated to the rest of the system
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
-        assertEquals(Set.of(new PackageAndUser(TARGET, USER)),
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
+        assertEquals(Set.of(UserPackage.of(USER, TARGET)),
                 impl.setEnabled(IDENTIFIER, true, USER));
 
         // request succeeded, but nothing changed
@@ -239,9 +238,9 @@
 
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -259,9 +258,9 @@
 
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -276,9 +275,9 @@
     public void testConfigSignaturePolicyNoConfig() throws Exception {
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -292,9 +291,9 @@
     @Test
     public void testConfigSignaturePolicyNoRefPkg() throws Exception {
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -312,9 +311,9 @@
 
         addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 301697d..bba7669 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -28,6 +28,7 @@
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayInfo.State;
 import android.content.om.OverlayableInfo;
+import android.content.pm.UserPackage;
 import android.os.FabricatedOverlayInfo;
 import android.os.FabricatedOverlayInternal;
 import android.text.TextUtils;
@@ -164,7 +165,7 @@
      * @throws IllegalStateException if the package is currently installed
      */
     void installAndAssert(@NonNull FakeDeviceState.PackageBuilder pkg, int userId,
-            @NonNull Set<PackageAndUser> onAddedUpdatedPackages)
+            @NonNull Set<UserPackage> onAddedUpdatedPackages)
             throws OperationFailedException {
         if (mState.select(pkg.packageName, userId) != null) {
             throw new IllegalStateException("package " + pkg.packageName + " already installed");
@@ -185,8 +186,8 @@
      * @throws IllegalStateException if the package is not currently installed
      */
     void upgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId,
-            @NonNull Set<PackageAndUser> onReplacingUpdatedPackages,
-            @NonNull Set<PackageAndUser> onReplacedUpdatedPackages)
+            @NonNull Set<UserPackage> onReplacingUpdatedPackages,
+            @NonNull Set<UserPackage> onReplacedUpdatedPackages)
             throws OperationFailedException {
         final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
         if (replacedPackage == null) {
@@ -207,7 +208,7 @@
      * @throws IllegalStateException if the package is not currently installed
      */
     void uninstallAndAssert(@NonNull String packageName, int userId,
-            @NonNull Set<PackageAndUser> onRemovedUpdatedPackages) {
+            @NonNull Set<UserPackage> onRemovedUpdatedPackages) {
         final FakeDeviceState.Package pkg = mState.select(packageName, userId);
         if (pkg == null) {
             throw new IllegalStateException("package " + packageName + " not installed");
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index fdf9354..0805485 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -74,6 +74,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
@@ -97,7 +98,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -692,9 +692,9 @@
 
     protected Map<String, PackageInfo> mInjectedPackages;
 
-    protected Set<PackageWithUser> mUninstalledPackages;
-    protected Set<PackageWithUser> mDisabledPackages;
-    protected Set<PackageWithUser> mEphemeralPackages;
+    protected Set<UserPackage> mUninstalledPackages;
+    protected Set<UserPackage> mDisabledPackages;
+    protected Set<UserPackage> mEphemeralPackages;
     protected Set<String> mSystemPackages;
 
     protected PackageManager mMockPackageManager;
@@ -1200,28 +1200,28 @@
         if (ENABLE_DUMP) {
             Log.v(TAG, "Uninstall package " + packageName + " / " + userId);
         }
-        mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
+        mUninstalledPackages.add(UserPackage.of(userId, packageName));
     }
 
     protected void installPackage(int userId, String packageName) {
         if (ENABLE_DUMP) {
             Log.v(TAG, "Install package " + packageName + " / " + userId);
         }
-        mUninstalledPackages.remove(PackageWithUser.of(userId, packageName));
+        mUninstalledPackages.remove(UserPackage.of(userId, packageName));
     }
 
     protected void disablePackage(int userId, String packageName) {
         if (ENABLE_DUMP) {
             Log.v(TAG, "Disable package " + packageName + " / " + userId);
         }
-        mDisabledPackages.add(PackageWithUser.of(userId, packageName));
+        mDisabledPackages.add(UserPackage.of(userId, packageName));
     }
 
     protected void enablePackage(int userId, String packageName) {
         if (ENABLE_DUMP) {
             Log.v(TAG, "Enable package " + packageName + " / " + userId);
         }
-        mDisabledPackages.remove(PackageWithUser.of(userId, packageName));
+        mDisabledPackages.remove(UserPackage.of(userId, packageName));
     }
 
     PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
@@ -1239,17 +1239,17 @@
         ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid);
         ret.applicationInfo.packageName = pi.packageName;
 
-        if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
+        if (mUninstalledPackages.contains(UserPackage.of(userId, packageName))) {
             ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
         }
-        if (mEphemeralPackages.contains(PackageWithUser.of(userId, packageName))) {
+        if (mEphemeralPackages.contains(UserPackage.of(userId, packageName))) {
             ret.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT;
         }
         if (mSystemPackages.contains(packageName)) {
             ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
         }
         ret.applicationInfo.enabled =
-                !mDisabledPackages.contains(PackageWithUser.of(userId, packageName));
+                !mDisabledPackages.contains(UserPackage.of(userId, packageName));
 
         if (getSignatures) {
             ret.signatures = null;
diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
index eaa0e9b..f0d389b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
@@ -34,6 +34,7 @@
 
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.pkg.PackageStateUnserialized;
 import com.android.server.pm.pkg.PackageUserStateImpl;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
@@ -46,12 +47,16 @@
 
     private boolean mCompatibilityModeEnabled;;
     private PackageImpl mMockAndroidPackage;
+    private PackageSetting mMockPackageState;
     private PackageUserStateImpl mMockUserState;
 
     @Before
     public void setUp() {
         mCompatibilityModeEnabled = ParsingPackageUtils.sCompatibilityModeEnabled;
         mMockAndroidPackage = mock(PackageImpl.class);
+        mMockPackageState = mock(PackageSetting.class);
+        when(mMockPackageState.getTransientState())
+                .thenReturn(new PackageStateUnserialized(mMockPackageState));
         mMockUserState = new PackageUserStateImpl();
         mMockUserState.setInstalled(true);
     }
@@ -221,7 +226,7 @@
         info.flags |= flags;
         when(mMockAndroidPackage.toAppInfoWithoutState()).thenReturn(info);
         return PackageInfoUtils.generateApplicationInfo(mMockAndroidPackage,
-                0 /*flags*/, mMockUserState, 0 /*userId*/, null);
+                0 /*flags*/, mMockUserState, 0 /*userId*/, mMockPackageState);
     }
 
     private void setGlobalCompatibilityMode(boolean enabled) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 9ce99d6..59f27ec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -676,27 +676,32 @@
         final File testFile = extractFile(TEST_APP4_APK);
         try {
             final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            var pkgSetting = mockPkgSetting(pkg);
             ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserStateInternal.DEFAULT, 0, null);
+                    PackageUserStateInternal.DEFAULT, 0, pkgSetting);
             for (ParsedActivity activity : pkg.getActivities()) {
                 assertNotNull(activity.getMetaData());
                 assertNull(PackageInfoUtils.generateActivityInfo(pkg, activity, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedProvider provider : pkg.getProviders()) {
                 assertNotNull(provider.getMetaData());
                 assertNull(PackageInfoUtils.generateProviderInfo(pkg, provider, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedActivity receiver : pkg.getReceivers()) {
                 assertNotNull(receiver.getMetaData());
                 assertNull(PackageInfoUtils.generateActivityInfo(pkg, receiver, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedService service : pkg.getServices()) {
                 assertNotNull(service.getMetaData());
                 assertNull(PackageInfoUtils.generateServiceInfo(pkg, service, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
         } finally {
             testFile.delete();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 96c0d0a..b20c63c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -83,6 +83,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
+import android.content.pm.UserPackage;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
@@ -107,7 +108,6 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.pm.ShortcutService.ConfigConstants;
 import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
@@ -4081,12 +4081,12 @@
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_1),
-                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_1),
+                        UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_1", "s0_2");
@@ -4113,12 +4113,12 @@
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_1),
-                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_1),
+                        UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_1", "s0_2");
@@ -4145,12 +4145,12 @@
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_1),
-                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_1),
+                        UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
@@ -4176,11 +4176,11 @@
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
@@ -4205,11 +4205,11 @@
         assertEquals(set(CALLING_PACKAGE_1),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
@@ -4234,8 +4234,8 @@
         assertEquals(set(CALLING_PACKAGE_1),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
                 set(),
@@ -4263,8 +4263,8 @@
         assertEquals(set(),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(set(),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
@@ -5584,12 +5584,12 @@
 
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
-                PackageWithUser.of(USER_0, LAUNCHER_1)));
+                UserPackage.of(USER_0, LAUNCHER_1)));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
-                PackageWithUser.of(USER_0, LAUNCHER_2)));
+                UserPackage.of(USER_0, LAUNCHER_2)));
 
-        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
-        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+        assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_0, LAUNCHER_3)));
+        assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_P0, LAUNCHER_1)));
 
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class),
                 anyString());
@@ -6146,12 +6146,12 @@
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
-                PackageWithUser.of(USER_0, LAUNCHER_1)));
+                UserPackage.of(USER_0, LAUNCHER_1)));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
-                PackageWithUser.of(USER_0, LAUNCHER_2)));
+                UserPackage.of(USER_0, LAUNCHER_2)));
 
-        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
-        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+        assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_0, LAUNCHER_3)));
+        assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_P0, LAUNCHER_1)));
 
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class),
                 anyString());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index c786784..15fd73c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -39,6 +39,7 @@
 import android.content.pm.Capability;
 import android.content.pm.CapabilityParams;
 import android.content.pm.ShortcutInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
@@ -51,7 +52,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.frameworks.servicestests.R;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import java.io.File;
 import java.io.FileWriter;
@@ -2413,7 +2413,7 @@
             assertWith(mManager.getDynamicShortcuts()).isEmpty();
         });
         // Make package 1 ephemeral.
-        mEphemeralPackages.add(PackageWithUser.of(USER_0, CALLING_PACKAGE_1));
+        mEphemeralPackages.add(UserPackage.of(USER_0, CALLING_PACKAGE_1));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertExpectException(IllegalStateException.class, "Ephemeral apps", () -> {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 8efcc41..8d2cffa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -59,9 +59,11 @@
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
+                .setShowInSettings(45)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
+        actualProps.setShowInSettings(32);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -99,10 +101,12 @@
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .setStartWithParent(true)
+                .setShowInSettings(3452)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
         orig.setShowInLauncher(2841);
         orig.setStartWithParent(false);
+        orig.setShowInSettings(1437);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
         for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -140,6 +144,9 @@
         assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
+        assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
+                hasManagePermission);
+
         // Items requiring hasQueryPermission - put them here using hasQueryPermission.
 
         // Items with no permission requirements.
@@ -182,5 +189,6 @@
         assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
         assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
         assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+        assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index c1e778d..df2c5dd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -599,6 +599,7 @@
         // Check that this new user has the expected properties (relative to the defaults)
         // provided that the test caller has the necessary permissions.
         assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+        assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
         assertThrows(SecurityException.class, userProps::getStartWithParent);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 9cd97ff3..699601b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -20,21 +20,16 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.apex.ApexInfo;
 import android.content.Context;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionInfo;
-import android.content.pm.SigningDetails;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.FileUtils;
-import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
 import android.util.SparseIntArray;
@@ -53,7 +48,6 @@
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import com.google.common.truth.Expect;
 
@@ -63,7 +57,6 @@
 
 import java.io.File;
 import java.io.InputStream;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -567,54 +560,6 @@
     }
 
     @Test
-    public void testApexPackageInfoGeneration() throws Exception {
-        String apexModuleName = "com.android.tzdata.apex";
-        File apexFile = copyRawResourceToFile(apexModuleName,
-                R.raw.com_android_tzdata);
-        ApexInfo apexInfo = new ApexInfo();
-        apexInfo.isActive = true;
-        apexInfo.isFactory = false;
-        apexInfo.moduleName = apexModuleName;
-        apexInfo.modulePath = apexFile.getPath();
-        apexInfo.versionCode = 191000070;
-        int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
-
-        ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile,
-                flags, Collections.emptyList(), false /*collectCertificates*/);
-        if (result.isError()) {
-            throw new IllegalStateException(result.getErrorMessage(), result.getException());
-        }
-
-        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-        ParsedPackage pkg = result.getResult();
-        ParseResult<SigningDetails> ret = ParsingPackageUtils.getSigningDetails(
-                input, pkg, false /*skipVerify*/);
-        if (ret.isError()) {
-            throw new IllegalStateException(ret.getErrorMessage(), ret.getException());
-        }
-        pkg.setSigningDetails(ret.getResult());
-        PackageInfo pi = PackageInfoUtils.generate(pkg.setApex(true).hideAsFinal(), apexInfo,
-                flags, null, UserHandle.USER_SYSTEM);
-
-        assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
-        assertTrue(pi.applicationInfo.enabled);
-        assertEquals(28, pi.applicationInfo.targetSdkVersion);
-        assertEquals(191000070, pi.applicationInfo.longVersionCode);
-        assertNotNull(pi.applicationInfo.metaData);
-        assertEquals(apexFile.getPath(), pi.applicationInfo.sourceDir);
-        assertEquals("Bundle[{com.android.vending.derived.apk.id=1}]",
-                pi.applicationInfo.metaData.toString());
-
-        assertEquals("com.google.android.tzdata", pi.packageName);
-        assertEquals(191000070, pi.getLongVersionCode());
-        assertNotNull(pi.signingInfo);
-        assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
-        assertTrue(pi.isApex);
-        assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
-        assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
-    }
-
-    @Test
     public void testUsesSdk() throws Exception {
         ParsedPackage pkg;
         SparseIntArray minExtVers;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
index a98a43b..93464cd 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
@@ -25,7 +25,7 @@
 import android.app.time.TimeCapabilitiesAndConfig;
 import android.app.time.TimeConfiguration;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -33,17 +33,17 @@
 /** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
 public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
 
-    private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+    private final List<StateChangeListener> mConfigurationInternalChangeListeners =
             new ArrayList<>();
     private ConfigurationInternal mConfigurationInternal;
 
     @Override
-    public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void addConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.add(listener);
     }
 
     @Override
-    public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.remove(listener);
     }
 
@@ -86,7 +86,7 @@
     }
 
     void simulateConfigurationChangeForTests() {
-        for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
+        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
             listener.onChange();
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 62dae48..caef494 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -40,7 +40,7 @@
 
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -1821,7 +1821,7 @@
         private long mElapsedRealtimeMillis;
         private long mSystemClockMillis;
         private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
-        private ConfigurationChangeListener mConfigurationInternalChangeListener;
+        private StateChangeListener mConfigurationInternalChangeListener;
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
@@ -1837,7 +1837,7 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+        public void setConfigurationInternalChangeListener(StateChangeListener listener) {
             mConfigurationInternalChangeListener = Objects.requireNonNull(listener);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 7140097..153d746 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -57,7 +57,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_autoDetectionSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
@@ -82,10 +83,7 @@
             assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -101,7 +99,7 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -117,10 +115,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -136,7 +132,7 @@
             assertEquals(CAPABILITY_NOT_APPLICABLE,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -150,7 +146,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_autoDetectNotSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(false)
                 .setGeoDetectionFeatureSupported(false)
@@ -175,10 +172,7 @@
             assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             if (userRestrictionsExpected) {
@@ -189,7 +183,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -205,10 +199,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             if (userRestrictionsExpected) {
@@ -219,7 +211,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -233,7 +225,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_geoDetectNotSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
@@ -258,10 +251,7 @@
             assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -276,7 +266,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -292,10 +282,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -308,7 +296,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -316,7 +304,8 @@
 
     @Test
     public void test_telephonyFallbackSupported() {
-        ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal config = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
@@ -331,7 +320,8 @@
     /** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
     @Test
     public void test_geoDetectionRunInBackgroundEnabled() {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
index fdee86e..fc6afe4 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
@@ -16,14 +16,11 @@
 
 package com.android.server.timezonedetector;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 
 import java.time.Duration;
@@ -31,76 +28,104 @@
 import java.util.List;
 import java.util.Optional;
 
-/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
+/**
+ * A partially implemented, fake implementation of ServiceConfigAccessor for tests.
+ *
+ * <p>This class has rudamentary support for multiple users, but unlike the real thing, it doesn't
+ * simulate that some settings are global and shared between users. It also delivers config updates
+ * synchronously.
+ */
 public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
 
-    private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+    private final List<StateChangeListener> mConfigurationInternalChangeListeners =
             new ArrayList<>();
-    private ConfigurationInternal mConfigurationInternal;
+    private ConfigurationInternal mCurrentUserConfigurationInternal;
+    private ConfigurationInternal mOtherUserConfigurationInternal;
 
     @Override
-    public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void addConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.add(listener);
     }
 
     @Override
-    public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.remove(listener);
     }
 
     @Override
     public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mConfigurationInternal;
+        return getConfigurationInternal(mCurrentUserConfigurationInternal.getUserId());
     }
 
     @Override
     public boolean updateConfiguration(
-            @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges,
+            @UserIdInt int userId, @NonNull TimeZoneConfiguration requestedChanges,
             boolean bypassUserPolicyChecks) {
-        assertNotNull(mConfigurationInternal);
+        assertNotNull(mCurrentUserConfigurationInternal);
         assertNotNull(requestedChanges);
 
+        ConfigurationInternal toUpdate = getConfigurationInternal(userId);
+
         // Simulate the real strategy's behavior: the new configuration will be updated to be the
-        // old configuration merged with the new if the user has the capability to up the settings.
-        // Then, if the configuration changed, the change listener is invoked.
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                mConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
-        TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+        // old configuration merged with the new if the user has the capability to update the
+        // settings. Then, if the configuration changed, the change listener is invoked.
+        TimeZoneCapabilities capabilities = toUpdate.asCapabilities(bypassUserPolicyChecks);
+        TimeZoneConfiguration configuration = toUpdate.asConfiguration();
         TimeZoneConfiguration newConfiguration =
                 capabilities.tryApplyConfigChanges(configuration, requestedChanges);
         if (newConfiguration == null) {
             return false;
         }
 
-        if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
-            mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
-
+        if (!newConfiguration.equals(configuration)) {
+            ConfigurationInternal updatedConfiguration = toUpdate.merge(newConfiguration);
+            if (updatedConfiguration.getUserId() == mCurrentUserConfigurationInternal.getUserId()) {
+                mCurrentUserConfigurationInternal = updatedConfiguration;
+            } else if (mOtherUserConfigurationInternal != null
+                    && updatedConfiguration.getUserId()
+                    == mOtherUserConfigurationInternal.getUserId()) {
+                mOtherUserConfigurationInternal = updatedConfiguration;
+            }
             // Note: Unlike the real strategy, the listeners are invoked synchronously.
-            simulateConfigurationChangeForTests();
+            notifyConfigurationChange();
         }
         return true;
     }
 
-    void initializeConfiguration(ConfigurationInternal configurationInternal) {
-        mConfigurationInternal = configurationInternal;
+    void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
     }
 
-    void simulateConfigurationChangeForTests() {
-        for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
-            listener.onChange();
-        }
+    void initializeOtherUserConfiguration(ConfigurationInternal configurationInternal) {
+        mOtherUserConfigurationInternal = configurationInternal;
+    }
+
+    void simulateCurrentUserConfigurationInternalChange(
+            ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
+        // Note: Unlike the real strategy, the listeners are invoked synchronously.
+        notifyConfigurationChange();
+    }
+
+    void simulateOtherUserConfigurationInternalChange(ConfigurationInternal configurationInternal) {
+        mOtherUserConfigurationInternal = configurationInternal;
+        // Note: Unlike the real strategy, the listeners are invoked synchronously.
+        notifyConfigurationChange();
     }
 
     @Override
     public ConfigurationInternal getConfigurationInternal(int userId) {
-        assertEquals("Multi-user testing not supported currently",
-                userId, mConfigurationInternal.getUserId());
-        return mConfigurationInternal;
+        if (userId == mCurrentUserConfigurationInternal.getUserId()) {
+            return mCurrentUserConfigurationInternal;
+        } else if (mOtherUserConfigurationInternal != null
+                    && userId == mOtherUserConfigurationInternal.getUserId()) {
+            return mOtherUserConfigurationInternal;
+        }
+        throw new AssertionError("userId not known: " + userId);
     }
 
     @Override
-    public void addLocationTimeZoneManagerConfigListener(ConfigurationChangeListener listener) {
+    public void addLocationTimeZoneManagerConfigListener(StateChangeListener listener) {
         failUnimplemented();
     }
 
@@ -206,9 +231,14 @@
         failUnimplemented();
     }
 
+    private void notifyConfigurationChange() {
+        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
+            listener.onChange();
+        }
+    }
+
     @SuppressWarnings("UnusedReturnValue")
     private static <T> T failUnimplemented() {
-        fail("Unimplemented");
-        return null;
+        throw new AssertionError("Unimplemented");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index 228dc95..fed8b40 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -15,23 +15,71 @@
  */
 package com.android.server.timezonedetector;
 
+import static org.junit.Assert.assertEquals;
+
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.util.IndentingPrintWriter;
 
+import java.util.ArrayList;
+
 public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
+    private final FakeServiceConfigAccessor mFakeServiceConfigAccessor =
+            new FakeServiceConfigAccessor();
+    private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
     private TimeZoneState mTimeZoneState;
 
+    public FakeTimeZoneDetectorStrategy() {
+        mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
+                this::notifyChangeListeners);
+    }
+
+    public void initializeConfiguration(ConfigurationInternal configuration) {
+        mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+    }
+
     @Override
     public boolean confirmTimeZone(String timeZoneId) {
         return false;
     }
 
     @Override
+    public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
+            boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal =
+                mFakeServiceConfigAccessor.getCurrentUserConfigurationInternal();
+        assertEquals("Multi-user testing not supported",
+                configurationInternal.getUserId(), userId);
+        return new TimeZoneCapabilitiesAndConfig(
+                configurationInternal.asCapabilities(bypassUserPolicyChecks),
+                configurationInternal.asConfiguration());
+    }
+
+    @Override
+    public boolean updateConfiguration(int userId, TimeZoneConfiguration requestedChanges,
+            boolean bypassUserPolicyChecks) {
+        return mFakeServiceConfigAccessor.updateConfiguration(
+                userId, requestedChanges, bypassUserPolicyChecks);
+    }
+
+    @Override
+    public void addChangeListener(StateChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    private void notifyChangeListeners() {
+        for (StateChangeListener listener : mListeners) {
+            listener.onChange();
+        }
+    }
+
+    @Override
     public TimeZoneState getTimeZoneState() {
         return mTimeZoneState;
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index 782eebf..223c532 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -161,7 +161,8 @@
 
     private static ConfigurationInternal createConfigurationInternal(
             boolean enhancedMetricsCollectionEnabled) {
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
index 21c9685..eb6f00c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
@@ -66,10 +66,14 @@
     /**
      * Waits for all enqueued work to be completed before returning.
      */
-    public void waitForMessagesToBeProcessed() throws InterruptedException {
+    public void waitForMessagesToBeProcessed() {
         synchronized (mMonitor) {
             if (mMessagesSent != mMessagesProcessed) {
-                mMonitor.wait();
+                try {
+                    mMonitor.wait();
+                } catch (InterruptedException e) {
+                    throw new AssertionError("Unexpected exception", e);
+                }
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 276fdb9..8909832 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -50,7 +50,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCurrentUserIdentityInjector mTestCurrentUserIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
 
     private TimeZoneDetectorInternalImpl mTimeZoneDetectorInternal;
@@ -65,12 +64,11 @@
         mTestHandler = new TestHandler(mHandlerThread.getLooper());
         mTestCurrentUserIdentityInjector = new TestCurrentUserIdentityInjector();
         mTestCurrentUserIdentityInjector.initializeCurrentUserId(ARBITRARY_USER_ID);
-        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
         mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
 
         mTimeZoneDetectorInternal = new TimeZoneDetectorInternalImpl(
                 mMockContext, mTestHandler, mTestCurrentUserIdentityInjector,
-                mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+                mFakeTimeZoneDetectorStrategySpy);
     }
 
     @After
@@ -83,17 +81,20 @@
     public void testGetCapabilitiesAndConfigForDpm() throws Exception {
         final boolean autoDetectionEnabled = true;
         ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
 
         TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
 
         int expectedUserId = mTestCurrentUserIdentityInjector.getCurrentUserId();
-        verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
+        final boolean expectedBypassUserPolicyChecks = true;
+        verify(mFakeTimeZoneDetectorStrategySpy).getCapabilitiesAndConfig(
+                expectedUserId, expectedBypassUserPolicyChecks);
 
-        final boolean bypassUserPolicyChecks = true;
         TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
-                testConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+                new TimeZoneCapabilitiesAndConfig(
+                        testConfig.asCapabilities(expectedBypassUserPolicyChecks),
+                        testConfig.asConfiguration());
         assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
     }
 
@@ -102,7 +103,7 @@
         final boolean autoDetectionEnabled = false;
         ConfigurationInternal initialConfigurationInternal =
                 createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
 
         TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
@@ -110,7 +111,7 @@
         assertTrue(mTimeZoneDetectorInternal.updateConfigurationForDpm(timeConfiguration));
 
         final boolean expectedBypassUserPolicyChecks = true;
-        verify(mFakeServiceConfigAccessorSpy).updateConfiguration(
+        verify(mFakeTimeZoneDetectorStrategySpy).updateConfiguration(
                 mTestCurrentUserIdentityInjector.getCurrentUserId(),
                 timeConfiguration,
                 expectedBypassUserPolicyChecks);
@@ -148,7 +149,8 @@
     }
 
     private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index bb9d564..d8346ee 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -69,7 +69,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCallerIdentityInjector mTestCallerIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
 
 
@@ -85,12 +84,11 @@
         mTestCallerIdentityInjector = new TestCallerIdentityInjector();
         mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
 
-        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
         mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
 
         mTimeZoneDetectorService = new TimeZoneDetectorService(
                 mMockContext, mTestHandler, mTestCallerIdentityInjector,
-                mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+                mFakeTimeZoneDetectorStrategySpy);
     }
 
     @After
@@ -115,7 +113,7 @@
 
         ConfigurationInternal configuration =
                 createConfigurationInternal(true /* autoDetectionEnabled*/);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
 
         TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -124,11 +122,14 @@
                 eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
 
         int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
-        verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
-
         boolean expectedBypassUserPolicyChecks = false;
+        verify(mFakeTimeZoneDetectorStrategySpy)
+                .getCapabilitiesAndConfig(expectedUserId, expectedBypassUserPolicyChecks);
+
         TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
-                configuration.createCapabilitiesAndConfig(expectedBypassUserPolicyChecks);
+                new TimeZoneCapabilitiesAndConfig(
+                        configuration.asCapabilities(expectedBypassUserPolicyChecks),
+                        configuration.asConfiguration());
         assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
     }
 
@@ -160,7 +161,7 @@
     public void testListenerRegistrationAndCallbacks() throws Exception {
         ConfigurationInternal initialConfiguration =
                 createConfigurationInternal(false /* autoDetectionEnabled */);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
 
         IBinder mockListenerBinder = mock(IBinder.class);
         ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -455,7 +456,8 @@
         // Default geo detection settings from auto detection settings - they are not important to
         // the tests.
         final boolean geoDetectionEnabled = autoDetectionEnabled;
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index d0a7c92..f50e7fb 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -38,19 +38,28 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
+import android.os.HandlerThread;
 
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
 import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -97,7 +106,8 @@
     };
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -110,7 +120,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -123,7 +134,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(false)
                     .setGeoDetectionFeatureSupported(false)
                     .setTelephonyFallbackSupported(false)
@@ -136,7 +148,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -149,7 +162,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -162,7 +176,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -174,14 +189,223 @@
                     .setGeoDetectionEnabledSetting(true)
                     .build();
 
-    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
+    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeEnvironment mFakeEnvironment;
+    private HandlerThread mHandlerThread;
+    private TestHandler mTestHandler;
+
+    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
 
     @Before
     public void setUp() {
         mFakeEnvironment = new FakeEnvironment();
-        mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED_GEO_DISABLED);
-        mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
+        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(
+                CONFIG_AUTO_DISABLED_GEO_DISABLED);
+
+        // Create a thread + handler for processing the work that the strategy posts.
+        mHandlerThread = new HandlerThread("TimeZoneDetectorStrategyImplTest");
+        mHandlerThread.start();
+        mTestHandler = new TestHandler(mHandlerThread.getLooper());
+        mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(
+                mFakeServiceConfigAccessorSpy, mTestHandler, mFakeEnvironment);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        mHandlerThread.join();
+    }
+
+    @Test
+    public void testChangeListenerBehavior_currentUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+        // The strategy initializes itself with the current user's config during construction.
+        assertEquals(currentUserConfig,
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+        boolean bypassUserPolicyChecks = false;
+
+        // Report a config change, but not one that actually changes anything.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_DISABLED_GEO_DISABLED);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(0);
+            assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Report a config change that actually changes something.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_ENABLED_GEO_ENABLED);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(1);
+            stateChangeListener.resetNotificationsReceivedCount();
+            assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Perform a (current user) update via the strategy.
+        {
+            TimeZoneConfiguration requestedChanges =
+                    new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    USER_ID, requestedChanges, bypassUserPolicyChecks);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(1);
+            stateChangeListener.resetNotificationsReceivedCount();
+        }
+    }
+
+    // Perform a (not current user) update via the strategy. There's no listener behavior for
+    // updates to "other" users.
+    @Test
+    public void testChangeListenerBehavior_otherUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+        // The strategy initializes itself with the current user's config during construction.
+        assertEquals(currentUserConfig,
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+        boolean bypassUserPolicyChecks = false;
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.initializeOtherUserConfiguration(otherUserConfig);
+
+        TimeZoneConfiguration requestedChanges =
+                new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+        mTimeZoneDetectorStrategy.updateConfiguration(
+                otherUserId, requestedChanges, bypassUserPolicyChecks);
+        mTestHandler.waitForMessagesToBeProcessed();
+
+        // Only changes to the current user's config are notified.
+        stateChangeListener.assertNotificationsReceived(0);
+        stateChangeListener.resetNotificationsReceivedCount();
+    }
+
+    // Current user behavior: the strategy caches and returns the latest configuration.
+    @Test
+    public void testReadAndWriteConfiguration_currentUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+        mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                currentUserConfig);
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+        reset(mFakeServiceConfigAccessorSpy);
+
+        final boolean bypassUserPolicyChecks = false;
+
+        ConfigurationInternal cachedConfigurationInternal =
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+        assertEquals(currentUserConfig, cachedConfigurationInternal);
+
+        // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+        {
+            reset(mFakeServiceConfigAccessorSpy);
+            TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+                    mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                            currentUserConfig.getUserId(), bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal(
+                    currentUserConfig.getUserId());
+
+            assertEquals(currentUserConfig.asCapabilities(bypassUserPolicyChecks),
+                    actualCapabilitiesAndConfig.getCapabilities());
+            assertEquals(currentUserConfig.asConfiguration(),
+                    actualCapabilitiesAndConfig.getConfiguration());
+        }
+
+        // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates
+        // the cached copy.
+        {
+            boolean newGeoDetectionEnabled =
+                    !cachedConfigurationInternal.asConfiguration().isGeoDetectionEnabled();
+            TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(newGeoDetectionEnabled)
+                    .build();
+            ConfigurationInternal expectedConfigAfterChange =
+                    new ConfigurationInternal.Builder(cachedConfigurationInternal)
+                            .setGeoDetectionEnabledSetting(newGeoDetectionEnabled)
+                            .build();
+
+            reset(mFakeServiceConfigAccessorSpy);
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            assertEquals(expectedConfigAfterChange,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+    }
+
+    // Not current user behavior: the strategy reads from the ServiceConfigAccessor.
+    @Test
+    public void testReadAndWriteConfiguration_otherUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+        mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                currentUserConfig);
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+        reset(mFakeServiceConfigAccessorSpy);
+
+        final boolean bypassUserPolicyChecks = false;
+
+        // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+        {
+            reset(mFakeServiceConfigAccessorSpy);
+            TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+                    mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                            otherUserId, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).getConfigurationInternal(otherUserId);
+
+            assertEquals(otherUserConfig.asCapabilities(bypassUserPolicyChecks),
+                    actualCapabilitiesAndConfig.getCapabilities());
+            assertEquals(otherUserConfig.asConfiguration(),
+                    actualCapabilitiesAndConfig.getConfiguration());
+        }
+
+        // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and doesn't
+        // touch the cached copy.
+        {
+            ConfigurationInternal cachedConfigBeforeChange =
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+            boolean newGeoDetectionEnabled =
+                    !otherUserConfig.asConfiguration().isGeoDetectionEnabled();
+            TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(newGeoDetectionEnabled)
+                    .build();
+
+            reset(mFakeServiceConfigAccessorSpy);
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            assertEquals(cachedConfigBeforeChange,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
     }
 
     @Test
@@ -1201,19 +1425,13 @@
 
         private final TestState<String> mTimeZoneId = new TestState<>();
         private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
-        private ConfigurationInternal mConfigurationInternal;
         private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
-        private ConfigurationChangeListener mConfigurationInternalChangeListener;
 
         FakeEnvironment() {
             // Ensure the fake environment starts with the defaults a fresh device would.
             initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
         }
 
-        void initializeConfig(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-        }
-
         void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
             mElapsedRealtimeMillis = elapsedRealtimeMillis;
         }
@@ -1228,16 +1446,6 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
-            mConfigurationInternalChangeListener = listener;
-        }
-
-        @Override
-        public ConfigurationInternal getCurrentUserConfigurationInternal() {
-            return mConfigurationInternal;
-        }
-
-        @Override
         public String getDeviceTimeZone() {
             return mTimeZoneId.getLatest();
         }
@@ -1254,11 +1462,6 @@
             mTimeZoneConfidence.set(confidence);
         }
 
-        void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-            mConfigurationInternalChangeListener.onChange();
-        }
-
         void assertTimeZoneNotChanged() {
             mTimeZoneId.assertHasNotBeenSet();
             mTimeZoneConfidence.assertHasNotBeenSet();
@@ -1322,7 +1525,8 @@
          * Simulates the user / user's configuration changing.
          */
         Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    configurationInternal);
             return this;
         }
 
@@ -1331,7 +1535,7 @@
          */
         Script simulateSetAutoMode(boolean autoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
-                    mFakeEnvironment.getCurrentUserConfigurationInternal())
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
                     .setAutoDetectionEnabledSetting(autoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
@@ -1343,7 +1547,7 @@
          */
         Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
-                    mFakeEnvironment.getCurrentUserConfigurationInternal())
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
                     .setGeoDetectionEnabledSetting(geoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
@@ -1457,4 +1661,22 @@
             @MatchType int matchType, @Quality int quality, int expectedScore) {
         return new TelephonyTestCase(matchType, quality, expectedScore);
     }
+
+    private static class TestStateChangeListener implements StateChangeListener {
+
+        private int mNotificationsReceived;
+
+        @Override
+        public void onChange() {
+            mNotificationsReceived++;
+        }
+
+        public void resetNotificationsReceivedCount() {
+            mNotificationsReceived = 0;
+        }
+
+        public void assertNotificationsReceived(int expectedCount) {
+            assertEquals(expectedCount, mNotificationsReceived);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index 2c3a7c4..042e3ef8 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -42,7 +42,8 @@
 
     private static ConfigurationInternal createUserConfig(
             @UserIdInt int userId, boolean geoDetectionEnabledSetting) {
-        return new ConfigurationInternal.Builder(userId)
+        return new ConfigurationInternal.Builder()
+                .setUserId(userId)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4ec9762..3a8e1cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2884,6 +2884,7 @@
         fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
         task.addChild(taskFragment1, POSITION_TOP);
         assertEquals(task, activity1.mStartingData.mAssociatedTask);
+        assertEquals(activity1.mStartingData, task.mSharedStartingData);
 
         final TaskFragment taskFragment2 = new TaskFragment(
                 mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2903,7 +2904,6 @@
 
         verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
                 eq(task.mSurfaceControl));
-        assertEquals(activity1.mStartingData, startingWindow.mStartingData);
         assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
         assertEquals(taskFragment1.getBounds(), activity1.getBounds());
         // The activity was resized by task fragment, but starting window must still cover the task.
@@ -2914,6 +2914,7 @@
         activity1.onFirstWindowDrawn(activityWindow);
         activity2.onFirstWindowDrawn(activityWindow);
         assertNull(activity1.mStartingWindow);
+        assertNull(task.mSharedStartingData);
     }
 
     @Test
@@ -2989,10 +2990,10 @@
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
         final TestWindowState startingWindow = createWindowState(attrs, activity);
-        activity.startingDisplayed = true;
+        activity.mStartingData = mock(StartingData.class);
         activity.addWindow(startingWindow);
         assertTrue("Starting window should be present", activity.hasStartingWindow());
-        activity.startingDisplayed = false;
+        activity.mStartingData = null;
         assertTrue("Starting window should be present", activity.hasStartingWindow());
 
         activity.removeChild(startingWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 513791d..0332c4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -1300,6 +1300,8 @@
         activity.allDrawn = true;
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
+        // Assume the activity contains a window.
+        doReturn(true).when(activity).hasChild();
         // Make sure activity can create remote animation target.
         doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
                 any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index f61effa..d55e53c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -321,7 +321,6 @@
         final ActivityRecord activity2 = createActivityRecord(dc2);
 
         activity1.allDrawn = true;
-        activity1.startingDisplayed = true;
         activity1.startingMoved = true;
 
         // Simulate activity resume / finish flows to prepare app transition & set visibility,
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index bc319db..e3b389b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -98,12 +98,19 @@
     @Test
     public void backTypeCrossTaskWhenBackToPreviousTask() {
         Task taskA = createTask(mDefaultDisplay);
-        createActivityRecord(taskA);
+        ActivityRecord recordA = createActivityRecord(taskA);
+        Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+
         withSystemCallback(createTopTaskWithActivity());
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
+
+        // verify if back animation would start.
+        verify(mBackNavigationController).scheduleAnimationLocked(
+                eq(BackNavigationInfo.TYPE_CROSS_TASK), any(), eq(mBackAnimationAdapter),
+                any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index ef84a4b..e85b574 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -24,7 +24,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -139,34 +138,12 @@
     }
 
     @Test
-    public void testDimAboveNoChildCreatesSurface() {
-        final float alpha = 0.8f;
-        mDimmer.dimAbove(mTransaction, alpha);
-
-        SurfaceControl dimLayer = getDimLayer();
-
-        assertNotNull("Dimmer should have created a surface", dimLayer);
-
-        verify(mTransaction).setAlpha(dimLayer, alpha);
-        verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE);
-    }
-
-    @Test
-    public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() {
-        float alpha = 0.8f;
-        mDimmer.dimAbove(mTransaction, alpha);
-        final SurfaceControl firstSurface = getDimLayer();
-
-        alpha = 0.9f;
-        mDimmer.dimAbove(mTransaction, alpha);
-
-        assertEquals(firstSurface, getDimLayer());
-        verify(mTransaction).setAlpha(firstSurface, 0.9f);
-    }
-
-    @Test
     public void testUpdateDimsAppliesCrop() {
-        mDimmer.dimAbove(mTransaction, 0.8f);
+        TestWindowContainer child = new TestWindowContainer(mWm);
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
 
         int width = 100;
         int height = 300;
@@ -178,17 +155,6 @@
     }
 
     @Test
-    public void testDimAboveNoChildNotReset() {
-        mDimmer.dimAbove(mTransaction, 0.8f);
-        SurfaceControl dimLayer = getDimLayer();
-        mDimmer.resetDimStates();
-
-        mDimmer.updateDims(mTransaction, new Rect());
-        verify(mTransaction).show(getDimLayer());
-        verify(mTransaction, never()).remove(dimLayer);
-    }
-
-    @Test
     public void testDimAboveWithChildCreatesSurfaceAboveChild() {
         TestWindowContainer child = new TestWindowContainer(mWm);
         mHost.addChild(child, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index b1acae2..eab2e15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -42,7 +42,6 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Build/Install/Run:
@@ -66,55 +65,6 @@
     }
 
     @Test
-    public void testCollectTasksByLastActiveTime() {
-        // Create a number of stacks with tasks (of incrementing active time)
-        final ArrayList<DisplayContent> displays = new ArrayList<>();
-        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
-        displays.add(display);
-
-        final int numStacks = 2;
-        for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
-            final Task stack = new TaskBuilder(mSupervisor)
-                    .setDisplay(display)
-                    .setOnTop(false)
-                    .build();
-        }
-
-        final int numTasks = 10;
-        int activeTime = 0;
-        final List<Task> rootTasks = new ArrayList<>();
-        display.getDefaultTaskDisplayArea().forAllRootTasks(task -> {
-            rootTasks.add(task);
-        }, false /* traverseTopToBottom */);
-        for (int i = 0; i < numTasks; i++) {
-            final Task task =
-                    createTask(rootTasks.get(i % numStacks), ".Task" + i, i, activeTime++, null);
-            doReturn(false).when(task).isVisible();
-        }
-
-        // Ensure that the latest tasks were returned in order of decreasing last active time,
-        // collected from all tasks across all the stacks
-        final int numFetchTasks = 5;
-        ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
-        mRunningTasks.getTasks(5, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
-                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
-        assertThat(tasks).hasSize(numFetchTasks);
-        for (int i = 0; i < numFetchTasks; i++) {
-            assertEquals(numTasks - i - 1, tasks.get(i).id);
-        }
-
-        // Ensure that requesting more than the total number of tasks only returns the subset
-        // and does not crash
-        tasks.clear();
-        mRunningTasks.getTasks(100, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
-                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
-        assertThat(tasks).hasSize(numTasks);
-        for (int i = 0; i < numTasks; i++) {
-            assertEquals(numTasks - i - 1, tasks.get(i).id);
-        }
-    }
-
-    @Test
     public void testTaskInfo_expectNoExtrasByDefault() {
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
         final int numTasks = 10;
@@ -125,7 +75,7 @@
                     .build();
             final Bundle data = new Bundle();
             data.putInt("key", 100);
-            createTask(stack, ".Task" + i, i, i, data);
+            createTask(stack, ".Task" + i, i, data);
         }
 
         final int numFetchTasks = 5;
@@ -150,7 +100,7 @@
                     .build();
             final Bundle data = new Bundle();
             data.putInt("key", 100);
-            createTask(stack, ".Task" + i, i, i, data);
+            createTask(stack, ".Task" + i, i, data);
         }
 
         final int numFetchTasks = 5;
@@ -167,46 +117,63 @@
     }
 
     @Test
-    public void testUpdateLastActiveTimeOfVisibleTasks() {
+    public void testGetTasksSortByFocusAndVisibility() {
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
+        final Task stack = new TaskBuilder(mSupervisor)
+                .setDisplay(display)
+                .setOnTop(true)
+                .build();
+
         final int numTasks = 10;
         final ArrayList<Task> tasks = new ArrayList<>();
         for (int i = 0; i < numTasks; i++) {
-            final Task task = createTask(null, ".Task" + i, i, i, null);
+            final Task task = createTask(stack, ".Task" + i, i, null);
             doReturn(false).when(task).isVisible();
             tasks.add(task);
         }
 
-        final Task visibleTask = tasks.get(0);
-        doReturn(true).when(visibleTask).isVisible();
-
-        final Task focusedTask = tasks.get(1);
+        final Task focusedTask = tasks.get(numTasks - 1);
         doReturn(true).when(focusedTask).isVisible();
-        doReturn(true).when(focusedTask).isFocused();
+        display.mFocusedApp = focusedTask.getTopNonFinishingActivity();
 
-        // Ensure that the last active time of visible tasks were updated while the focused one had
-        // the largest last active time.
+        final Task visibleTaskTop = tasks.get(numTasks - 2);
+        doReturn(true).when(visibleTaskTop).isVisible();
+
+        final Task visibleTaskBottom = tasks.get(numTasks - 3);
+        doReturn(true).when(visibleTaskBottom).isVisible();
+
+        // Ensure that the focused Task is on top, visible tasks below, then invisible tasks.
         final int numFetchTasks = 5;
         final ArrayList<RunningTaskInfo> fetchTasks = new ArrayList<>();
         mRunningTasks.getTasks(numFetchTasks, fetchTasks,
                 FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
                 mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
         assertThat(fetchTasks).hasSize(numFetchTasks);
-        assertEquals(fetchTasks.get(0).id, focusedTask.mTaskId);
-        assertEquals(fetchTasks.get(1).id, visibleTask.mTaskId);
+        for (int i = 0; i < numFetchTasks; i++) {
+            assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+        }
+
+        // Ensure that requesting more than the total number of tasks only returns the subset
+        // and does not crash
+        fetchTasks.clear();
+        mRunningTasks.getTasks(100, fetchTasks,
+                FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
+                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
+        assertThat(fetchTasks).hasSize(numTasks);
+        for (int i = 0; i < numTasks; i++) {
+            assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+        }
     }
 
     /**
-     * Create a task with a single activity in it, with the given last active time.
+     * Create a task with a single activity in it.
      */
-    private Task createTask(Task stack, String className, int taskId,
-            int lastActiveTime, Bundle extras) {
+    private Task createTask(Task stack, String className, int taskId, Bundle extras) {
         final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
                 .setComponent(new ComponentName(mContext.getPackageName(), className))
                 .setTaskId(taskId)
                 .setParentTaskFragment(stack)
                 .build();
-        task.lastActiveTime = lastActiveTime;
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setTask(task)
                 .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
@@ -227,7 +194,7 @@
                     .setDisplay(i % 2 == 0 ? display0 : display1)
                     .setOnTop(true)
                     .build();
-            final Task task = createTask(stack, ".Task" + i, i, i, null);
+            final Task task = createTask(stack, ".Task" + i, i, null);
             tasks.add(task);
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index d59fce0..c906abc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -87,7 +87,6 @@
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 import android.view.InsetsFrameProvider;
-import android.view.InsetsVisibilities;
 import android.view.WindowManager;
 
 import androidx.test.filters.MediumTest;
@@ -2302,8 +2301,7 @@
         // We should get a null LetterboxDetails object as there is no letterboxed activity, so
         // nothing will get passed to SysUI
         verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), isNull());
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), isNull());
 
     }
 
@@ -2331,8 +2329,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
         verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     @Test
@@ -2367,8 +2364,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal();
         verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     @Test
@@ -2420,8 +2416,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
         verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     private void recomputeNaturalConfigurationOfUnresizableActivity() {
diff --git a/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java b/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java
new file mode 100644
index 0000000..cea3d87
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.server.utils.EventLogger;
+
+import java.util.List;
+
+/**
+ * Writes logs to {@link DualDumpOutputStream}.
+ *
+ * @see EventLogger.DumpSink
+ * @see DualDumpOutputStream
+ */
+final class DualOutputStreamDumpSink implements EventLogger.DumpSink {
+
+    private final long mId;
+    private final DualDumpOutputStream mDumpOutputStream;
+
+    /* package */ DualOutputStreamDumpSink(DualDumpOutputStream dualDumpOutputStream, long id) {
+        mDumpOutputStream = dualDumpOutputStream;
+        mId = id;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void sink(String tag, List<EventLogger.Event> events) {
+        mDumpOutputStream.write("USB Event Log", mId, tag);
+        for (EventLogger.Event evt: events) {
+            mDumpOutputStream.write("USB Event", mId, evt.toString());
+        }
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceLogger.java b/services/usb/java/com/android/server/usb/UsbDeviceLogger.java
deleted file mode 100644
index fab00bc..0000000
--- a/services/usb/java/com/android/server/usb/UsbDeviceLogger.java
+++ /dev/null
@@ -1,143 +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.server.usb;
-
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.dump.DualDumpOutputStream;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.LinkedList;
-
-/**
-* Constructor UsbDeviceLogger class
-*/
-public class UsbDeviceLogger {
-    private final Object mLock = new Object();
-
-    // ring buffer of events to log.
-    @GuardedBy("mLock")
-    private final LinkedList<Event> mEvents;
-
-    private final String mTitle;
-
-    // the maximum number of events to keep in log
-    private final int mMemSize;
-
-    /**
-     * Constructor for Event class.
-     */
-    public abstract static class Event {
-        // formatter for timestamps
-        private static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
-
-        private final Calendar mCalendar;
-
-        Event() {
-            mCalendar = Calendar.getInstance();
-        }
-
-    /**
-     * Convert event to String
-     * @return StringBuilder
-     */
-        public String toString() {
-            return (new StringBuilder(String.format("%tm-%td %tH:%tM:%tS.%tL",
-                    mCalendar, mCalendar, mCalendar, mCalendar, mCalendar, mCalendar)))
-                    .append(" ").append(eventToString()).toString();
-        }
-
-        /**
-         * Causes the string message for the event to appear in the logcat.
-         * Here is an example of how to create a new event (a StringEvent), adding it to the logger
-         * (an instance of UsbDeviceLoggerr) while also making it show in the logcat:
-         * <pre>
-         *     myLogger.log(
-         *         (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
-         * </pre>
-         * @param tag the tag for the android.util.Log.v
-         * @return the same instance of the event
-         */
-        public Event printLog(String tag) {
-            Log.i(tag, eventToString());
-            return this;
-        }
-
-        /**
-         * Convert event to String.
-         * This method is only called when the logger history is about to the dumped,
-         * so this method is where expensive String conversions should be made, not when the Event
-         * subclass is created.
-         * Timestamp information will be automatically added, do not include it.
-         * @return a string representation of the event that occurred.
-         */
-        public abstract String eventToString();
-    }
-
-    /**
-    * Constructor StringEvent class
-    */
-    public static class StringEvent extends Event {
-        private final String mMsg;
-
-        public StringEvent(String msg) {
-            mMsg = msg;
-        }
-
-        @Override
-        public String eventToString() {
-            return mMsg;
-        }
-    }
-
-    /**
-     * Constructor for logger.
-     * @param size the maximum number of events to keep in log
-     * @param title the string displayed before the recorded log
-     */
-    public UsbDeviceLogger(int size, String title) {
-        mEvents = new LinkedList<Event>();
-        mMemSize = size;
-        mTitle = title;
-    }
-
-    /**
-     * Constructor for logger.
-     * @param evt the maximum number of events to keep in log
-     */
-    public synchronized void log(Event evt) {
-        synchronized (mLock) {
-            if (mEvents.size() >= mMemSize) {
-                mEvents.removeFirst();
-            }
-            mEvents.add(evt);
-        }
-    }
-
-    /**
-     * Constructor for logger.
-     * @param dump the maximum number of events to keep in log
-     * @param id the category of events
-     */
-    public synchronized void dump(DualDumpOutputStream dump, long id) {
-        dump.write("USB Event Log", id, mTitle);
-        for (Event evt : mEvents) {
-            dump.write("USB Event", id, evt.toString());
-        }
-    }
-}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e90a376..1c081c1 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -91,6 +91,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.utils.EventLogger;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.File;
@@ -213,7 +214,7 @@
     private static Set<Integer> sDenyInterfaces;
     private HashMap<Long, FileDescriptor> mControlFds;
 
-    private static UsbDeviceLogger sEventLogger;
+    private static EventLogger sEventLogger;
 
     static {
         sDenyInterfaces = new HashSet<>();
@@ -238,7 +239,7 @@
         public void onUEvent(UEventObserver.UEvent event) {
             if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
             if (sEventLogger != null) {
-                sEventLogger.log(new UsbDeviceLogger.StringEvent("USB UEVENT: "
+                sEventLogger.enqueue(new EventLogger.StringEvent("USB UEVENT: "
                         + event.toString()));
             } else {
                 if (DEBUG) Slog.d(TAG, "sEventLogger == null");
@@ -395,7 +396,7 @@
         mUEventObserver.startObserving(USB_STATE_MATCH);
         mUEventObserver.startObserving(ACCESSORY_START_MATCH);
 
-        sEventLogger = new UsbDeviceLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity");
+        sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity");
     }
 
     UsbProfileGroupSettingsManager getCurrentSettings() {
@@ -837,7 +838,7 @@
 
         protected void sendStickyBroadcast(Intent intent) {
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-            sEventLogger.log(new UsbDeviceLogger.StringEvent("USB intent: " + intent));
+            sEventLogger.enqueue(new EventLogger.StringEvent("USB intent: " + intent));
         }
 
         private void updateUsbFunctions() {
@@ -2350,7 +2351,7 @@
 
         if (mHandler != null) {
             mHandler.dump(dump, "handler", UsbDeviceManagerProto.HANDLER);
-            sEventLogger.dump(dump, UsbHandlerProto.UEVENT);
+            sEventLogger.dump(new DualOutputStreamDumpSink(dump, UsbHandlerProto.UEVENT));
         }
 
         dump.end(token);
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index 47b09fe..f916660 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -62,6 +62,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.utils.EventLogger;
 
 import libcore.io.IoUtils;
 
@@ -130,7 +131,7 @@
     @GuardedBy("mLock")
     private boolean mIsWriteSettingsScheduled;
 
-    private static UsbDeviceLogger sEventLogger;
+    private static EventLogger sEventLogger;
 
     /**
      * A package of a user.
@@ -263,7 +264,7 @@
 
         mUsbHandlerManager = usbResolveActivityManager;
 
-        sEventLogger = new UsbDeviceLogger(DUMPSYS_LOG_BUFFER,
+        sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER,
                 "UsbProfileGroupSettingsManager activity");
     }
 
@@ -970,7 +971,7 @@
                     matches, mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)));
         }
 
-        sEventLogger.log(new UsbDeviceLogger.StringEvent("accessoryAttached: " + intent));
+        sEventLogger.enqueue(new EventLogger.StringEvent("accessoryAttached: " + intent));
         resolveActivity(intent, matches, defaultActivity, null, accessory);
     }
 
@@ -1524,7 +1525,8 @@
             }
         }
 
-        sEventLogger.dump(dump, UsbProfileGroupSettingsManagerProto.INTENT);
+        sEventLogger.dump(new DualOutputStreamDumpSink(dump,
+                UsbProfileGroupSettingsManagerProto.INTENT));
         dump.end(token);
     }
 
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index c083e90..af67637 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -23,6 +23,14 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsLocalVoiceInteraction",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ]
 }
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index dc695d6..e19117b 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -730,6 +730,25 @@
     }
 
     /**
+     * Result code to string
+     *
+     * @param result The result code.
+     * @return The result code in string format.
+     *
+     * @hide
+     */
+    public static String resultToString(@Result int result) {
+        switch (result) {
+            case RESULT_OK: return "OK";
+            case RESULT_MUST_DEACTIVATE_SIM : return "MUST_DEACTIVATE_SIM";
+            case RESULT_RESOLVABLE_ERRORS: return "RESOLVABLE_ERRORS";
+            case RESULT_FIRST_USER: return "FIRST_USER";
+            default:
+            return "UNKNOWN(" + result + ")";
+        }
+    }
+
+    /**
      * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
      */
     private class IEuiccServiceWrapper extends IEuiccService.Stub {
diff --git a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
index 9add38e..46a049c 100644
--- a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
+++ b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
@@ -123,4 +123,16 @@
     public int describeContents() {
         return 0;
     }
+
+    /**
+     * @hide
+     *
+     * @return String representation of {@link GetEuiccProfileInfoListResult}
+     */
+    @Override
+    public String toString() {
+        return "[GetEuiccProfileInfoListResult: result=" + EuiccService.resultToString(result)
+                + ", isRemovable=" + mIsRemovable + ", mProfiles=" + Arrays.toString(mProfiles)
+                + "]";
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/DomainSelectionService.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/DomainSelectionService.aidl
index 1550ab3..b9d2ba8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/DomainSelectionService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,9 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package com.android.systemui.shared.system;
+package android.telephony;
 
-parcelable RemoteTransitionCompat;
+parcelable DomainSelectionService.SelectionAttributes;
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
new file mode 100644
index 0000000..383561a
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.Annotation.PreciseDisconnectCauses;
+import android.telephony.ims.ImsReasonInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IDomainSelectionServiceController;
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Main domain selection implementation for various telephony features.
+ *
+ * The telephony framework will bind to the {@link DomainSelectionService}.
+ *
+ * @hide
+ */
+public class DomainSelectionService extends Service {
+
+    private static final String LOG_TAG = "DomainSelectionService";
+
+    /**
+     * The intent that must be defined as an intent-filter in the AndroidManifest of the
+     * {@link DomainSelectionService}.
+     *
+     * @hide
+     */
+    public static final String SERVICE_INTERFACE = "android.telephony.DomainSelectionService";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SELECTOR_TYPE_",
+            value = {
+                    SELECTOR_TYPE_CALLING,
+                    SELECTOR_TYPE_SMS,
+                    SELECTOR_TYPE_UT})
+    public @interface SelectorType {}
+
+    /** Indicates the domain selector type for calling. */
+    public static final int SELECTOR_TYPE_CALLING = 1;
+    /** Indicates the domain selector type for sms. */
+    public static final int SELECTOR_TYPE_SMS = 2;
+    /** Indicates the domain selector type for supplementary services. */
+    public static final int SELECTOR_TYPE_UT = 3;
+
+    /** Indicates that the modem can scan for emergency service as per modem’s implementation. */
+    public static final int SCAN_TYPE_NO_PREFERENCE = 0;
+
+    /** Indicates that the modem will scan for emergency service in limited service mode. */
+    public static final int SCAN_TYPE_LIMITED_SERVICE = 1;
+
+    /** Indicates that the modem will scan for emergency service in full service mode. */
+    public static final int SCAN_TYPE_FULL_SERVICE = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SCAN_TYPE_",
+            value = {
+                    SCAN_TYPE_NO_PREFERENCE,
+                    SCAN_TYPE_LIMITED_SERVICE,
+                    SCAN_TYPE_FULL_SERVICE})
+    public @interface EmergencyScanType {}
+
+    /**
+     * Contains attributes required to determine the domain for a telephony service.
+     */
+    public static final class SelectionAttributes implements Parcelable {
+
+        private static final String TAG = "SelectionAttributes";
+
+        private int mSlotId;
+        private int mSubId;
+        private @Nullable String mCallId;
+        private @Nullable String mNumber;
+        private @SelectorType int mSelectorType;
+        private boolean mIsVideoCall;
+        private boolean mIsEmergency;
+        private boolean mIsExitedFromAirplaneMode;
+        //private @Nullable UtAttributes mUtAttributes;
+        private @Nullable ImsReasonInfo mImsReasonInfo;
+        private @PreciseDisconnectCauses int mCause;
+        private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+        /**
+         * @param slotId The slot identifier.
+         * @param subId The subscription identifier.
+         * @param callId The call identifier.
+         * @param number The dialed number.
+         * @param selectorType Indicates the requested domain selector type.
+         * @param video Indicates it's a video call.
+         * @param emergency Indicates it's emergency service.
+         * @param exited {@code true} if the request caused the device to move out of airplane mode.
+         * @param imsReasonInfo The reason why the last PS attempt failed.
+         * @param cause The reason why the last CS attempt failed.
+         * @param regResult The current registration result for emergency services.
+         */
+        private SelectionAttributes(int slotId, int subId, @Nullable String callId,
+                @Nullable String number, @SelectorType int selectorType,
+                boolean video, boolean emergency, boolean exited,
+                /*UtAttributes attr,*/
+                @Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
+                @Nullable EmergencyRegResult regResult) {
+            mSlotId = slotId;
+            mSubId = subId;
+            mCallId = callId;
+            mNumber = number;
+            mSelectorType = selectorType;
+            mIsVideoCall = video;
+            mIsEmergency = emergency;
+            mIsExitedFromAirplaneMode = exited;
+            //mUtAttributes = attr;
+            mImsReasonInfo = imsReasonInfo;
+            mCause = cause;
+            mEmergencyRegResult = regResult;
+        }
+
+        /**
+         * Copy constructor.
+         *
+         * @param s Source selection attributes.
+         * @hide
+         */
+        public SelectionAttributes(@NonNull SelectionAttributes s) {
+            mSlotId = s.mSlotId;
+            mSubId = s.mSubId;
+            mCallId = s.mCallId;
+            mNumber = s.mNumber;
+            mSelectorType = s.mSelectorType;
+            mIsEmergency = s.mIsEmergency;
+            mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
+            //mUtAttributes = s.mUtAttributes;
+            mImsReasonInfo = s.mImsReasonInfo;
+            mCause = s.mCause;
+            mEmergencyRegResult = s.mEmergencyRegResult;
+        }
+
+        /**
+         * Constructs a SelectionAttributes object from the given parcel.
+         */
+        private SelectionAttributes(@NonNull Parcel in) {
+            readFromParcel(in);
+        }
+
+        /**
+         * @return The slot identifier.
+         */
+        public int getSlotId() {
+            return mSlotId;
+        }
+
+        /**
+         * @return The subscription identifier.
+         */
+        public int getSubId() {
+            return mSubId;
+        }
+
+        /**
+         * @return The call identifier.
+         */
+        public @Nullable String getCallId() {
+            return mCallId;
+        }
+
+        /**
+         * @return The dialed number.
+         */
+        public @Nullable String getNumber() {
+            return mNumber;
+        }
+
+        /**
+         * @return The domain selector type.
+         */
+        public @SelectorType int getSelectorType() {
+            return mSelectorType;
+        }
+
+        /**
+         * @return {@code true} if the request is for a video call.
+         */
+        public boolean isVideoCall() {
+            return mIsVideoCall;
+        }
+
+        /**
+         * @return {@code true} if the request is for emergency services.
+         */
+        public boolean isEmergency() {
+            return mIsEmergency;
+        }
+
+        /**
+         * @return {@code true} if the request caused the device to move out of airplane mode.
+         */
+        public boolean isExitedFromAirplaneMode() {
+            return mIsExitedFromAirplaneMode;
+        }
+
+        /*
+        public @Nullable UtAttributes getUtAttributes();
+            return mUtAttributes;
+        }
+        */
+
+        /**
+         * @return The PS disconnect cause if trying over PS resulted in a failure and
+         *         reselection is required.
+         */
+        public @Nullable ImsReasonInfo getPsDisconnectCause() {
+            return mImsReasonInfo;
+        }
+
+        /**
+         * @return The CS disconnect cause if trying over CS resulted in a failure and
+         *         reselection is required.
+         */
+        public @PreciseDisconnectCauses int getCsDisconnectCause() {
+            return mCause;
+        }
+
+        /**
+         * @return The current registration state of cellular network.
+         */
+        public @Nullable EmergencyRegResult getEmergencyRegResult() {
+            return mEmergencyRegResult;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "{ slotId=" + mSlotId
+                    + ", subId=" + mSubId
+                    + ", callId=" + mCallId
+                    + ", number=" + (Build.IS_DEBUGGABLE ? mNumber : "***")
+                    + ", type=" + mSelectorType
+                    + ", videoCall=" + mIsVideoCall
+                    + ", emergency=" + mIsEmergency
+                    + ", airplaneMode=" + mIsExitedFromAirplaneMode
+                    + ", reasonInfo=" + mImsReasonInfo
+                    + ", cause=" + mCause
+                    + ", regResult=" + mEmergencyRegResult
+                    + " }";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            SelectionAttributes that = (SelectionAttributes) o;
+            return mSlotId == that.mSlotId && mSubId == that.mSubId
+                    && TextUtils.equals(mCallId, that.mCallId)
+                    && TextUtils.equals(mNumber, that.mNumber)
+                    && mSelectorType == that.mSelectorType && mIsVideoCall == that.mIsVideoCall
+                    && mIsEmergency == that.mIsEmergency
+                    && mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
+                    //&& equalsHandlesNulls(mUtAttributes, that.mUtAttributes)
+                    && equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
+                    && mCause == that.mCause
+                    && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mCallId, mNumber, mImsReasonInfo,
+                    mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, mEmergencyRegResult,
+                    mSlotId, mSubId, mSelectorType, mCause);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            out.writeInt(mSlotId);
+            out.writeInt(mSubId);
+            out.writeString8(mCallId);
+            out.writeString8(mNumber);
+            out.writeInt(mSelectorType);
+            out.writeBoolean(mIsVideoCall);
+            out.writeBoolean(mIsEmergency);
+            out.writeBoolean(mIsExitedFromAirplaneMode);
+            //out.writeParcelable(mUtAttributes, 0);
+            out.writeParcelable(mImsReasonInfo, 0);
+            out.writeInt(mCause);
+            out.writeParcelable(mEmergencyRegResult, 0);
+        }
+
+        private void readFromParcel(@NonNull Parcel in) {
+            mSlotId = in.readInt();
+            mSubId = in.readInt();
+            mCallId = in.readString8();
+            mNumber = in.readString8();
+            mSelectorType = in.readInt();
+            mIsVideoCall = in.readBoolean();
+            mIsEmergency = in.readBoolean();
+            mIsExitedFromAirplaneMode = in.readBoolean();
+            //mUtAttributes = s.mUtAttributes;
+            mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
+                    android.telephony.ims.ImsReasonInfo.class);
+            mCause = in.readInt();
+            mEmergencyRegResult = in.readParcelable(EmergencyRegResult.class.getClassLoader(),
+                    EmergencyRegResult.class);
+        }
+
+        public static final @NonNull Creator<SelectionAttributes> CREATOR =
+                new Creator<SelectionAttributes>() {
+            @Override
+            public SelectionAttributes createFromParcel(@NonNull Parcel in) {
+                return new SelectionAttributes(in);
+            }
+
+            @Override
+            public SelectionAttributes[] newArray(int size) {
+                return new SelectionAttributes[size];
+            }
+        };
+
+        private static boolean equalsHandlesNulls(Object a, Object b) {
+            return (a == null) ? (b == null) : a.equals(b);
+        }
+
+        /**
+         * Builder class creating a new instance.
+         */
+        public static final class Builder {
+            private final int mSlotId;
+            private final int mSubId;
+            private @Nullable String mCallId;
+            private @Nullable String mNumber;
+            private final @SelectorType int mSelectorType;
+            private boolean mIsVideoCall;
+            private boolean mIsEmergency;
+            private boolean mIsExitedFromAirplaneMode;
+            //private @Nullable UtAttributes mUtAttributes;
+            private @Nullable ImsReasonInfo mImsReasonInfo;
+            private @PreciseDisconnectCauses int mCause;
+            private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+            /**
+             * Default constructor for Builder.
+             */
+            public Builder(int slotId, int subId, @SelectorType int selectorType) {
+                mSlotId = slotId;
+                mSubId = subId;
+                mSelectorType = selectorType;
+            }
+
+            /**
+             * Sets the call identifier.
+             *
+             * @param callId The call identifier.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setCallId(@NonNull String callId) {
+                mCallId = callId;
+                return this;
+            }
+
+            /**
+             * Sets the dialed number.
+             *
+             * @param number The dialed number.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setNumber(@NonNull String number) {
+                mNumber = number;
+                return this;
+            }
+
+            /**
+             * Sets whether it's a video call or not.
+             *
+             * @param video Indicates it's a video call.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setVideoCall(boolean video) {
+                mIsVideoCall = video;
+                return this;
+            }
+
+            /**
+             * Sets whether it's an emergency service or not.
+             *
+             * @param emergency Indicates it's emergency service.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setEmergency(boolean emergency) {
+                mIsEmergency = emergency;
+                return this;
+            }
+
+            /**
+             * Sets whether the request caused the device to move out of airplane mode.
+             *
+             * @param exited {@code true} if the request caused the device to move out of
+             *        airplane mode.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setExitedFromAirplaneMode(boolean exited) {
+                mIsExitedFromAirplaneMode = exited;
+                return this;
+            }
+
+            /**
+             * Sets the Ut service attributes.
+             * Only applicable for SELECTOR_TYPE_UT
+             *
+             * @param attr Ut services attributes.
+             * @return The same instance of the builder.
+             */
+            /*
+            public @NonNull Builder setUtAttributes(@NonNull UtAttributes attr);
+                mUtAttributes = attr;
+                return this;
+            }
+            */
+
+            /**
+             * Sets an optional reason why the last PS attempt failed.
+             *
+             * @param info The reason why the last PS attempt failed.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setPsDisconnectCause(@NonNull ImsReasonInfo info) {
+                mImsReasonInfo = info;
+                return this;
+            }
+
+            /**
+             * Sets an optional reason why the last CS attempt failed.
+             *
+             * @param cause The reason why the last CS attempt failed.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setCsDisconnectCause(@PreciseDisconnectCauses int cause) {
+                mCause = cause;
+                return this;
+            }
+
+            /**
+             * Sets the current registration result for emergency services.
+             *
+             * @param regResult The current registration result for emergency services.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setEmergencyRegResult(@NonNull EmergencyRegResult regResult) {
+                mEmergencyRegResult = regResult;
+                return this;
+            }
+
+            /**
+             * Build the SelectionAttributes.
+             * @return The SelectionAttributes object.
+             */
+            public @NonNull SelectionAttributes build() {
+                return new SelectionAttributes(mSlotId, mSubId, mCallId, mNumber, mSelectorType,
+                        mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, /*mUtAttributes,*/
+                        mImsReasonInfo, mCause, mEmergencyRegResult);
+            }
+        }
+    }
+
+    /**
+     * A wrapper class for ITransportSelectorCallback interface.
+     */
+    private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
+        private static final String TAG = "TransportSelectorCallbackWrapper";
+
+        private final @NonNull ITransportSelectorCallback mCallback;
+        private final @NonNull Executor mExecutor;
+
+        private @Nullable ITransportSelectorResultCallbackAdapter mResultCallback;
+        private @Nullable DomainSelectorWrapper mSelectorWrapper;
+
+        TransportSelectorCallbackWrapper(@NonNull ITransportSelectorCallback cb,
+                @NonNull Executor executor) {
+            mCallback = cb;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCreated(@NonNull DomainSelector selector) {
+            try {
+                mSelectorWrapper = new DomainSelectorWrapper(selector, mExecutor);
+                mCallback.onCreated(mSelectorWrapper.getCallbackBinder());
+            } catch (Exception e) {
+                Rlog.e(TAG, "onCreated e=" + e);
+            }
+        }
+
+        @Override
+        public void onWlanSelected() {
+            try {
+                mCallback.onWlanSelected();
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWlanSelected e=" + e);
+            }
+        }
+
+        @Override
+        public @NonNull WwanSelectorCallback onWwanSelected() {
+            WwanSelectorCallback callback = null;
+            try {
+                IWwanSelectorCallback cb = mCallback.onWwanSelected();
+                callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWwanSelected e=" + e);
+            }
+
+            return callback;
+        }
+
+        @Override
+        public void onWwanSelected(Consumer<WwanSelectorCallback> consumer) {
+            try {
+                mResultCallback = new ITransportSelectorResultCallbackAdapter(consumer, mExecutor);
+                mCallback.onWwanSelectedAsync(mResultCallback);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWwanSelected e=" + e);
+                executeMethodAsyncNoException(mExecutor,
+                        () -> consumer.accept(null), TAG, "onWwanSelectedAsync-Exception");
+            }
+        }
+
+        @Override
+        public void onSelectionTerminated(@DisconnectCauses int cause) {
+            try {
+                mCallback.onSelectionTerminated(cause);
+                mSelectorWrapper = null;
+            } catch (Exception e) {
+                Rlog.e(TAG, "onSelectionTerminated e=" + e);
+            }
+        }
+
+        private class ITransportSelectorResultCallbackAdapter
+                extends ITransportSelectorResultCallback.Stub {
+            private final @NonNull Consumer<WwanSelectorCallback> mConsumer;
+            private final @NonNull Executor mExecutor;
+
+            ITransportSelectorResultCallbackAdapter(
+                    @NonNull Consumer<WwanSelectorCallback> consumer,
+                    @NonNull Executor executor) {
+                mConsumer = consumer;
+                mExecutor = executor;
+            }
+
+            @Override
+            public void onCompleted(@NonNull IWwanSelectorCallback cb) {
+                if (mConsumer == null) return;
+
+                WwanSelectorCallback callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+                executeMethodAsyncNoException(mExecutor,
+                        () -> mConsumer.accept(callback), TAG, "onWwanSelectedAsync-Completed");
+            }
+        }
+    }
+
+    /**
+     * A wrapper class for IDomainSelector interface.
+     */
+    private final class DomainSelectorWrapper {
+        private static final String TAG = "DomainSelectorWrapper";
+
+        private @NonNull IDomainSelector mCallbackBinder;
+
+        DomainSelectorWrapper(@NonNull DomainSelector cb, @NonNull Executor executor) {
+            mCallbackBinder = new IDomainSelectorAdapter(cb, executor);
+        }
+
+        private class IDomainSelectorAdapter extends IDomainSelector.Stub {
+            private final @NonNull WeakReference<DomainSelector> mDomainSelectorWeakRef;
+            private final @NonNull Executor mExecutor;
+
+            IDomainSelectorAdapter(@NonNull DomainSelector domainSelector,
+                    @NonNull Executor executor) {
+                mDomainSelectorWeakRef =
+                        new WeakReference<DomainSelector>(domainSelector);
+                mExecutor = executor;
+            }
+
+            @Override
+            public void cancelSelection() {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.cancelSelection(), TAG, "cancelSelection");
+            }
+
+            @Override
+            public void reselectDomain(@NonNull SelectionAttributes attr) {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.reselectDomain(attr), TAG, "reselectDomain");
+            }
+
+            @Override
+            public void finishSelection() {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.finishSelection(), TAG, "finishSelection");
+            }
+        }
+
+        public @NonNull IDomainSelector getCallbackBinder() {
+            return mCallbackBinder;
+        }
+    }
+
+    /**
+     * A wrapper class for IWwanSelectorCallback and IWwanSelectorResultCallback.
+     */
+    private final class WwanSelectorCallbackWrapper
+            implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
+        private static final String TAG = "WwanSelectorCallbackWrapper";
+
+        private final @NonNull IWwanSelectorCallback mCallback;
+        private final @NonNull Executor mExecutor;
+
+        private @Nullable IWwanSelectorResultCallbackAdapter mResultCallback;
+
+        WwanSelectorCallbackWrapper(@NonNull IWwanSelectorCallback cb,
+                @NonNull Executor executor) {
+            mCallback = cb;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCancel() {
+            try {
+                mCallback.onCancel();
+            } catch (Exception e) {
+                Rlog.e(TAG, "onCancel e=" + e);
+            }
+        }
+
+        @Override
+        public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+                @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
+                @NonNull Consumer<EmergencyRegResult> consumer) {
+            try {
+                if (signal != null) signal.setOnCancelListener(this);
+                mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
+                mCallback.onRequestEmergencyNetworkScan(
+                        preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
+                        scanType, mResultCallback);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onRequestEmergencyNetworkScan e=" + e);
+            }
+        }
+
+        @Override
+        public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) {
+            try {
+                mCallback.onDomainSelected(accessNetworkType);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onDomainSelected e=" + e);
+            }
+        }
+
+        private class IWwanSelectorResultCallbackAdapter
+                extends IWwanSelectorResultCallback.Stub {
+            private final @NonNull Consumer<EmergencyRegResult> mConsumer;
+            private final @NonNull Executor mExecutor;
+
+            IWwanSelectorResultCallbackAdapter(@NonNull Consumer<EmergencyRegResult> consumer,
+                    @NonNull Executor executor) {
+                mConsumer = consumer;
+                mExecutor = executor;
+            }
+
+            @Override
+            public void onComplete(@NonNull EmergencyRegResult result) {
+                if (mConsumer == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> mConsumer.accept(result), TAG, "onScanComplete");
+            }
+        }
+    }
+
+    private final Object mExecutorLock = new Object();
+
+    /** Executor used to execute methods called remotely by the framework. */
+    private @NonNull Executor mExecutor;
+
+    /**
+     * Selects a domain for the given operation.
+     *
+     * @param attr Required to determine the domain.
+     * @param callback The callback instance being registered.
+     */
+    public void onDomainSelection(@NonNull SelectionAttributes attr,
+            @NonNull TransportSelectorCallback callback) {
+    }
+
+    /**
+     * Notifies the change in {@link ServiceState} for a specific slot.
+     *
+     * @param slotId For which the state changed.
+     * @param subId For which the state changed.
+     * @param serviceState Updated {@link ServiceState}.
+     */
+    public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
+    }
+
+    /**
+     * Notifies the change in {@link BarringInfo} for a specific slot.
+     *
+     * @param slotId For which the state changed.
+     * @param subId For which the state changed.
+     * @param info Updated {@link BarringInfo}.
+     */
+    public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo info) {
+    }
+
+    private final IBinder mDomainSelectionServiceController =
+            new IDomainSelectionServiceController.Stub() {
+        @Override
+        public void selectDomain(@NonNull SelectionAttributes attr,
+                @NonNull ITransportSelectorCallback callback)  throws RemoteException {
+            executeMethodAsync(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onDomainSelection(attr,
+                            new TransportSelectorCallbackWrapper(callback, getCachedExecutor())),
+                    LOG_TAG, "onDomainSelection");
+        }
+
+        @Override
+        public void updateServiceState(int slotId, int subId, @NonNull ServiceState serviceState) {
+            executeMethodAsyncNoException(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onServiceStateUpdated(slotId,
+                            subId, serviceState), LOG_TAG, "onServiceStateUpdated");
+        }
+
+        @Override
+        public void updateBarringInfo(int slotId, int subId, @NonNull BarringInfo info) {
+            executeMethodAsyncNoException(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onBarringInfoUpdated(slotId, subId, info),
+                    LOG_TAG, "onBarringInfoUpdated");
+        }
+    };
+
+    private static void executeMethodAsync(@NonNull Executor executor, @NonNull Runnable r,
+            @NonNull String tag, @NonNull String errorLogName) throws RemoteException {
+        try {
+            CompletableFuture.runAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+        } catch (CancellationException | CompletionException e) {
+            Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+            throw new RemoteException(e.getMessage());
+        }
+    }
+
+    private void executeMethodAsyncNoException(@NonNull Executor executor, @NonNull Runnable r,
+            @NonNull String tag, @NonNull String errorLogName) {
+        try {
+            CompletableFuture.runAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+        } catch (CancellationException | CompletionException e) {
+            Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+        }
+    }
+
+    /** @hide */
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            Log.i(LOG_TAG, "DomainSelectionService Bound.");
+            return mDomainSelectionServiceController;
+        }
+        return null;
+    }
+
+    /**
+     * The DomainSelectionService will be able to define an {@link Executor} that the service
+     * can use to execute the methods. It has set the default executor as Runnable::run,
+     *
+     * @return An {@link Executor} to be used.
+     */
+    @SuppressLint("OnNameExpected")
+    public @NonNull Executor getExecutor() {
+        return Runnable::run;
+    }
+
+    private @NonNull Executor getCachedExecutor() {
+        synchronized (mExecutorLock) {
+            if (mExecutor == null) {
+                Executor e = getExecutor();
+                mExecutor = (e != null) ? e : Runnable::run;
+            }
+            return mExecutor;
+        }
+    }
+
+    /**
+     * Returns a string representation of the domain.
+     * @param domain The domain.
+     * @return The name of the domain.
+     * @hide
+     */
+    public static @NonNull String getDomainName(@NetworkRegistrationInfo.Domain int domain) {
+        return NetworkRegistrationInfo.domainToString(domain);
+    }
+}
diff --git a/telephony/java/android/telephony/DomainSelector.java b/telephony/java/android/telephony/DomainSelector.java
new file mode 100644
index 0000000..0871831
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+
+/**
+ * Implemented as part of the {@link DomainSelectionService} to implement domain selection
+ * for a specific use case.
+ * @hide
+ */
+public interface DomainSelector {
+    /**
+     * Cancel an ongoing selection operation. It is up to the DomainSelectionService
+     * to clean up all ongoing operations with the framework.
+     */
+    void cancelSelection();
+
+    /**
+     * Reselect a domain due to the call not setting up properly.
+     *
+     * @param attr attributes required to select the domain.
+     */
+    void reselectDomain(@NonNull SelectionAttributes attr);
+
+    /**
+     * Finish the selection procedure and clean everything up.
+     */
+    void finishSelection();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/EmergencyRegResult.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/EmergencyRegResult.aidl
index 1550ab3..f722962 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/EmergencyRegResult.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,9 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package com.android.systemui.shared.system;
+package android.telephony;
 
-parcelable RemoteTransitionCompat;
+parcelable EmergencyRegResult;
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegResult.java
new file mode 100644
index 0000000..5aed412
--- /dev/null
+++ b/telephony/java/android/telephony/EmergencyRegResult.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Contains attributes required to determine the domain for a telephony service
+ * @hide
+ */
+public final class EmergencyRegResult implements Parcelable {
+
+    /**
+     * Indicates the cellular network type of the acquired system.
+     */
+    private @AccessNetworkConstants.RadioAccessNetworkType int mAccessNetworkType;
+
+    /**
+     * Registration state of the acquired system.
+     */
+    private @NetworkRegistrationInfo.RegistrationState int mRegState;
+
+    /**
+     * EMC domain indicates the current domain of the acquired system.
+     */
+    private @NetworkRegistrationInfo.Domain int mDomain;
+
+    /**
+     * Indicates whether the network supports voice over PS network.
+     */
+    private boolean mIsVopsSupported;
+
+    /**
+     * This indicates if camped network support VoLTE emergency bearers.
+     * This should only be set if the UE is in LTE mode.
+     */
+    private boolean mIsEmcBearerSupported;
+
+    /**
+     * The value of the network provided EMC in 5G Registration ACCEPT.
+     * This should be set only if the UE is in 5G mode.
+     */
+    private int mNwProvidedEmc;
+
+    /**
+     * The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * This should be set only if the UE is in 5G mode.
+     */
+    private int mNwProvidedEmf;
+
+    /** 3-digit Mobile Country Code, 000..999, empty string if unknown. */
+    private @NonNull String mMcc;
+
+    /** 2 or 3-digit Mobile Network Code, 00..999, empty string if unknown. */
+    private @NonNull String mMnc;
+
+    /**
+     * The ISO-3166-1 alpha-2 country code equivalent for the network's country code,
+     * empty string if unknown.
+     */
+    private @NonNull String mIso;
+
+    /**
+     * Constructor
+     * @param accessNetwork Indicates the network type of the acquired system.
+     * @param regState Indicates the registration state of the acquired system.
+     * @param domain Indicates the current domain of the acquired system.
+     * @param isVopsSupported Indicates whether the network supports voice over PS network.
+     * @param isEmcBearerSupported  Indicates if camped network support VoLTE emergency bearers.
+     * @param emc The value of the network provided EMC in 5G Registration ACCEPT.
+     * @param emf The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * @param mcc Mobile country code, empty string if unknown.
+     * @param mnc Mobile network code, empty string if unknown.
+     * @param iso The ISO-3166-1 alpha-2 country code equivalent, empty string if unknown.
+     * @hide
+     */
+    public EmergencyRegResult(
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+            @NetworkRegistrationInfo.RegistrationState int regState,
+            @NetworkRegistrationInfo.Domain int domain,
+            boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
+            @NonNull String mcc, @NonNull String mnc, @NonNull String iso) {
+        mAccessNetworkType = accessNetwork;
+        mRegState = regState;
+        mDomain = domain;
+        mIsVopsSupported = isVopsSupported;
+        mIsEmcBearerSupported = isEmcBearerSupported;
+        mNwProvidedEmc = emc;
+        mNwProvidedEmf = emf;
+        mMcc = mcc;
+        mMnc = mnc;
+        mIso = iso;
+    }
+
+    /**
+     * Copy constructors
+     *
+     * @param s Source emergency scan result
+     * @hide
+     */
+    public EmergencyRegResult(@NonNull EmergencyRegResult s) {
+        mAccessNetworkType = s.mAccessNetworkType;
+        mRegState = s.mRegState;
+        mDomain = s.mDomain;
+        mIsVopsSupported = s.mIsVopsSupported;
+        mIsEmcBearerSupported = s.mIsEmcBearerSupported;
+        mNwProvidedEmc = s.mNwProvidedEmc;
+        mNwProvidedEmf = s.mNwProvidedEmf;
+        mMcc = s.mMcc;
+        mMnc = s.mMnc;
+        mIso = s.mIso;
+    }
+
+    /**
+     * Construct a EmergencyRegResult object from the given parcel.
+     */
+    private EmergencyRegResult(@NonNull Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Returns the cellular access network type of the acquired system.
+     *
+     * @return the cellular network type.
+     */
+    public @AccessNetworkConstants.RadioAccessNetworkType int getAccessNetwork() {
+        return mAccessNetworkType;
+    }
+
+    /**
+     * Returns the registration state of the acquired system.
+     *
+     * @return the registration state.
+     */
+    public @NetworkRegistrationInfo.RegistrationState int getRegState() {
+        return mRegState;
+    }
+
+    /**
+     * Returns the current domain of the acquired system.
+     *
+     * @return the current domain.
+     */
+    public @NetworkRegistrationInfo.Domain int getDomain() {
+        return mDomain;
+    }
+
+    /**
+     * Returns whether the network supports voice over PS network.
+     *
+     * @return {@code true} if the network supports voice over PS network.
+     */
+    public boolean isVopsSupported() {
+        return mIsVopsSupported;
+    }
+
+    /**
+     * Returns whether camped network support VoLTE emergency bearers.
+     * This is not valid if the UE is not in LTE mode.
+     *
+     * @return {@code true} if the network supports VoLTE emergency bearers.
+     */
+    public boolean isEmcBearerSupported() {
+        return mIsEmcBearerSupported;
+    }
+
+    /**
+     * Returns the value of the network provided EMC in 5G Registration ACCEPT.
+     * This is not valid if UE is not in 5G mode.
+     *
+     * @return the value of the network provided EMC.
+     */
+    public int getNwProvidedEmc() {
+        return mNwProvidedEmc;
+    }
+
+    /**
+     * Returns the value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * This is not valid if UE is not in 5G mode.
+     *
+     * @return the value of the network provided EMF.
+     */
+    public int getNwProvidedEmf() {
+        return mNwProvidedEmf;
+    }
+
+    /**
+     * Returns 3-digit Mobile Country Code.
+     *
+     * @return Mobile Country Code.
+     */
+    public @NonNull String getMcc() {
+        return mMcc;
+    }
+
+    /**
+     * Returns 2 or 3-digit Mobile Network Code.
+     *
+     * @return Mobile Network Code.
+     */
+    public @NonNull String getMnc() {
+        return mMnc;
+    }
+
+    /**
+     * Returns the ISO-3166-1 alpha-2 country code is provided in lowercase 2 character format.
+     *
+     * @return Country code.
+     */
+    public @NonNull String getIso() {
+        return mIso;
+    }
+
+    @Override
+    public @NonNull String toString() {
+        return "{ accessNetwork="
+                + AccessNetworkConstants.AccessNetworkType.toString(mAccessNetworkType)
+                + ", regState=" + NetworkRegistrationInfo.registrationStateToString(mRegState)
+                + ", domain=" + NetworkRegistrationInfo.domainToString(mDomain)
+                + ", vops=" + mIsVopsSupported
+                + ", emcBearer=" + mIsEmcBearerSupported
+                + ", emc=" + mNwProvidedEmc
+                + ", emf=" + mNwProvidedEmf
+                + ", mcc=" + mMcc
+                + ", mnc=" + mMnc
+                + ", iso=" + mIso
+                + " }";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        EmergencyRegResult that = (EmergencyRegResult) o;
+        return mAccessNetworkType == that.mAccessNetworkType
+                && mRegState == that.mRegState
+                && mDomain == that.mDomain
+                && mIsVopsSupported == that.mIsVopsSupported
+                && mIsEmcBearerSupported == that.mIsEmcBearerSupported
+                && mNwProvidedEmc == that.mNwProvidedEmc
+                && mNwProvidedEmf == that.mNwProvidedEmf
+                && TextUtils.equals(mMcc, that.mMcc)
+                && TextUtils.equals(mMnc, that.mMnc)
+                && TextUtils.equals(mIso, that.mIso);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAccessNetworkType, mRegState, mDomain,
+                mIsVopsSupported, mIsEmcBearerSupported,
+                mNwProvidedEmc, mNwProvidedEmf,
+                mMcc, mMnc, mIso);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mAccessNetworkType);
+        out.writeInt(mRegState);
+        out.writeInt(mDomain);
+        out.writeBoolean(mIsVopsSupported);
+        out.writeBoolean(mIsEmcBearerSupported);
+        out.writeInt(mNwProvidedEmc);
+        out.writeInt(mNwProvidedEmf);
+        out.writeString8(mMcc);
+        out.writeString8(mMnc);
+        out.writeString8(mIso);
+    }
+
+    private void readFromParcel(@NonNull Parcel in) {
+        mAccessNetworkType = in.readInt();
+        mRegState = in.readInt();
+        mDomain = in.readInt();
+        mIsVopsSupported = in.readBoolean();
+        mIsEmcBearerSupported = in.readBoolean();
+        mNwProvidedEmc = in.readInt();
+        mNwProvidedEmf = in.readInt();
+        mMcc = in.readString8();
+        mMnc = in.readString8();
+        mIso = in.readString8();
+    }
+
+    public static final @NonNull Creator<EmergencyRegResult> CREATOR =
+            new Creator<EmergencyRegResult>() {
+        @Override
+        public EmergencyRegResult createFromParcel(@NonNull Parcel in) {
+            return new EmergencyRegResult(in);
+        }
+
+        @Override
+        public EmergencyRegResult[] newArray(int size) {
+            return new EmergencyRegResult[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java
new file mode 100644
index 0000000..d396790
--- /dev/null
+++ b/telephony/java/android/telephony/TransportSelectorCallback.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.Annotation.DisconnectCauses;
+
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the transport selection result.
+ * @hide
+ */
+public interface TransportSelectorCallback {
+    /**
+     * Notify that {@link DomainSelector} instance has been created for the selection request.
+     *
+     * @param selector the {@link DomainSelector} instance created.
+     */
+    void onCreated(@NonNull DomainSelector selector);
+
+    /**
+     * Notify that WLAN transport has been selected.
+     */
+    void onWlanSelected();
+
+    /**
+     * Notify that WWAN transport has been selected.
+     */
+    @NonNull WwanSelectorCallback onWwanSelected();
+
+    /**
+     * Notify that WWAN transport has been selected.
+     *
+     * @param consumer The callback to receive the result.
+     */
+    void onWwanSelected(Consumer<WwanSelectorCallback> consumer);
+
+    /**
+     * Notify that selection has terminated because there is no decision that can be made
+     * or a timeout has occurred. The call should be terminated when this method is called.
+     *
+     * @param cause indicates the reason.
+     */
+    void onSelectionTerminated(@DisconnectCauses int cause);
+}
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
new file mode 100644
index 0000000..489a589
--- /dev/null
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.CancellationSignal;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.DomainSelectionService.EmergencyScanType;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the domain selection result.
+ * @hide
+ */
+public interface WwanSelectorCallback {
+    /**
+     * Notify the framework that the {@link DomainSelectionService} has requested an emergency
+     * network scan as part of selection.
+     *
+     * @param preferredNetworks the ordered list of preferred networks to scan.
+     * @param scanType indicates the scan preference, such as full service or limited service.
+     * @param signal notifies when the operation is canceled.
+     * @param consumer the handler of the response.
+     */
+    void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+            @EmergencyScanType int scanType,
+            @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
+
+    /**
+     * Notifies the FW that the domain has been selected. After this method is called,
+     * this interface can be discarded.
+     *
+     * @param accessNetworkType the selected network type.
+     */
+    void onDomainSelected(@RadioAccessNetworkType int accessNetworkType);
+}
diff --git a/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
new file mode 100644
index 0000000..8ad79ed
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.BarringInfo;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.ServiceState;
+
+import com.android.internal.telephony.ITransportSelectorCallback;
+
+oneway interface IDomainSelectionServiceController {
+    void selectDomain(in SelectionAttributes attr, in ITransportSelectorCallback callback);
+    void updateServiceState(int slotId, int subId, in ServiceState serviceState);
+    void updateBarringInfo(int slotId, int subId, in BarringInfo info);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/IDomainSelector.aidl
index 1550ab3..d94840b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
 
-parcelable RemoteTransitionCompat;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+
+oneway interface IDomainSelector {
+    void cancelSelection();
+    void reselectDomain(in SelectionAttributes attr);
+    void finishSelection();
+}
diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
new file mode 100644
index 0000000..aca83f4
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+
+interface ITransportSelectorCallback {
+    oneway void onCreated(in IDomainSelector selector);
+    oneway void onWlanSelected();
+    IWwanSelectorCallback onWwanSelected();
+    oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
+    oneway void onSelectionTerminated(int cause);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
similarity index 67%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
index 1550ab3..1460b45 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
 
-parcelable RemoteTransitionCompat;
+import com.android.internal.telephony.IWwanSelectorCallback;
+
+oneway interface ITransportSelectorResultCallback {
+    void onCompleted(in IWwanSelectorCallback cb);
+}
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
new file mode 100644
index 0000000..65d994b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import com.android.internal.telephony.IWwanSelectorResultCallback;
+
+oneway interface IWwanSelectorCallback {
+    void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
+            int scanType, in IWwanSelectorResultCallback cb);
+    void onDomainSelected(int accessNetworkType);
+    void onCancel();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
similarity index 69%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
index 1550ab3..0d61fcb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
 
-parcelable RemoteTransitionCompat;
+import android.telephony.EmergencyRegResult;
+
+oneway interface IWwanSelectorResultCallback {
+    void onComplete(in EmergencyRegResult result);
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index b2f7be6..0f83a05 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -239,4 +239,10 @@
     public static final int CELL_OFF_FLAG = 0;
     public static final int CELL_ON_FLAG = 1;
     public static final int CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG = 2;
+
+    /** The key to specify the selected domain for dialing calls. */
+    public static final String EXTRA_DIAL_DOMAIN = "dial_domain";
+
+    /** The key to specify the emergency service category */
+    public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 6e935d1..83823ea 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -254,6 +254,8 @@
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
+            <meta-data android:name="android.app.shortcuts"
+                       android:resource="@xml/shortcuts" />
         </activity>
         <activity android:name=".SendNotificationActivity"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml
new file mode 100644
index 0000000..24830de
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Label for split screen shortcut-->
+    <string name="split_screen_shortcut_label">Split Screen Secondary Activity</string>
+</resources>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml
new file mode 100644
index 0000000..804ec99
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.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.
+  -->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
+    <shortcut
+        android:shortcutId="split_screen_shortcut"
+        android:shortcutShortLabel="@string/split_screen_shortcut_label">
+        <intent
+            android:action="android.intent.action.VIEW"
+            android:targetPackage="com.android.server.wm.flicker.testapp"
+            android:targetClass="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" />
+    </shortcut>
+</shortcuts>
\ No newline at end of file
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
index 29d87a2..ec1709c 100644
--- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -25,6 +25,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -98,6 +99,7 @@
     }
 
     @Test
+    @Ignore
     public void testBadUpdateRollback() throws Exception {
         // Need to switch user in order to send broadcasts in device tests
         assertTrue(getDevice().switchUser(mSecondaryUserId));
diff --git a/tests/RollbackTest/SampleRollbackApp/Android.bp b/tests/RollbackTest/SampleRollbackApp/Android.bp
index a18488d..074c7bc 100644
--- a/tests/RollbackTest/SampleRollbackApp/Android.bp
+++ b/tests/RollbackTest/SampleRollbackApp/Android.bp
@@ -29,4 +29,5 @@
     resource_dirs: ["res"],
     certificate: "platform",
     sdk_version: "system_current",
+    min_sdk_version: "29",
 }