Merge "Correct package installation behaviour in GameManagerService"
diff --git a/Android.bp b/Android.bp
index 7d02d7d..ee5db70 100644
--- a/Android.bp
+++ b/Android.bp
@@ -109,6 +109,7 @@
         ":platform-compat-native-aidl",
 
         // AIDL sources from external directories
+        ":android.hardware.graphics.common-V3-java-source",
         ":android.hardware.security.keymint-V1-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
         ":android.hardware.tv.tuner-V1-java-source",
@@ -288,6 +289,7 @@
             // TODO: remove when moved to the below package
             "frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
             "packages/modules/Connectivity/framework/aidl-export",
+            "hardware/interfaces/graphics/common/aidl",
         ],
     },
     dxflags: [
@@ -537,6 +539,7 @@
             // TODO: remove when moved to the below package
             "frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
             "packages/modules/Connectivity/framework/aidl-export",
+            "hardware/interfaces/graphics/common/aidl",
         ],
     },
     // These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/apex/OWNERS b/apex/OWNERS
index b3e81b9..e867586 100644
--- a/apex/OWNERS
+++ b/apex/OWNERS
@@ -1 +1 @@
-file:platform/packages/modules/common:/OWNERS
+file:platform/packages/modules/common:/OWNERS #{LAST_RESORT_SUGGESTION}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 96114dc..ffa534e 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -1376,6 +1376,11 @@
         }
     }
 
+    private boolean isAllowedBlobAccess(int uid, String packageName) {
+        return (!Process.isSupplemental(uid) && !Process.isIsolated(uid)
+                && !mPackageManagerInternal.isInstantApp(packageName, UserHandle.getUserId(uid)));
+    }
+
     private class PackageChangedReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1437,8 +1442,7 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
-            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
-                    packageName, UserHandle.getUserId(callingUid))) {
+            if (!isAllowedBlobAccess(callingUid, packageName)) {
                 throw new SecurityException("Caller not allowed to create session; "
                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
             }
@@ -1487,8 +1491,7 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
-            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
-                    packageName, UserHandle.getUserId(callingUid))) {
+            if (!isAllowedBlobAccess(callingUid, packageName)) {
                 throw new SecurityException("Caller not allowed to open blob; "
                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
             }
@@ -1519,8 +1522,7 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
-            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
-                    packageName, UserHandle.getUserId(callingUid))) {
+            if (!isAllowedBlobAccess(callingUid, packageName)) {
                 throw new SecurityException("Caller not allowed to open blob; "
                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
             }
@@ -1544,8 +1546,7 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
-            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
-                    packageName, UserHandle.getUserId(callingUid))) {
+            if (!isAllowedBlobAccess(callingUid, packageName)) {
                 throw new SecurityException("Caller not allowed to open blob; "
                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
             }
@@ -1628,8 +1629,7 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
-            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
-                    packageName, UserHandle.getUserId(callingUid))) {
+            if (!isAllowedBlobAccess(callingUid, packageName)) {
                 throw new SecurityException("Caller not allowed to open blob; "
                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 1620983..a5c2bcc 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -72,6 +72,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.database.ContentObserver;
@@ -1839,6 +1840,9 @@
                     if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) {
                         return false;
                     }
+                    if (hasUseExactAlarmPermission(a.packageName, a.uid)) {
+                        return false;
+                    }
                     return !isExemptFromExactAlarmPermission(a.uid);
                 };
                 removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
@@ -1900,6 +1904,9 @@
                                         || !isExactAlarmChangeEnabled(packageName, userId)) {
                                     return;
                                 }
+                                if (hasUseExactAlarmPermission(packageName, uid)) {
+                                    return;
+                                }
 
                                 final boolean requested = mExactAlarmCandidates.contains(
                                         UserHandle.getAppId(uid));
@@ -2534,6 +2541,8 @@
 
     private static boolean getScheduleExactAlarmState(boolean requested, boolean denyListed,
             int appOpMode) {
+        // This does not account for the state of the USE_EXACT_ALARM permission.
+        // The caller should do that separately.
         if (!requested) {
             return false;
         }
@@ -2543,7 +2552,16 @@
         return appOpMode == AppOpsManager.MODE_ALLOWED;
     }
 
+    boolean hasUseExactAlarmPermission(String packageName, int uid) {
+        return PermissionChecker.checkPermissionForPreflight(getContext(),
+                Manifest.permission.USE_EXACT_ALARM, PermissionChecker.PID_UNKNOWN, uid,
+                packageName) == PermissionChecker.PERMISSION_GRANTED;
+    }
+
     boolean hasScheduleExactAlarmInternal(String packageName, int uid) {
+        if (hasUseExactAlarmPermission(packageName, uid)) {
+            return true;
+        }
         // Not using getScheduleExactAlarmState as this can avoid some calls to AppOpsService.
         // Not using #mLastOpScheduleExactAlarm as it may contain stale values.
         // No locking needed as all internal containers being queried are immutable.
@@ -3759,6 +3777,9 @@
                 if (!isExactAlarmChangeEnabled(changedPackage, userId)) {
                     continue;
                 }
+                if (hasUseExactAlarmPermission(changedPackage, uid)) {
+                    continue;
+                }
                 final int appOpMode;
                 synchronized (mLock) {
                     appOpMode = mLastOpScheduleExactAlarm.get(uid,
@@ -3778,7 +3799,8 @@
                 }
                 if (added) {
                     synchronized (mLock) {
-                        removeExactAlarmsOnPermissionRevokedLocked(uid, changedPackage);
+                        removeExactAlarmsOnPermissionRevokedLocked(uid,
+                                changedPackage, /*killUid = */ true);
                     }
                 } else {
                     sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId);
@@ -3794,7 +3816,7 @@
      * This is not expected to get called frequently.
      */
     @GuardedBy("mLock")
-    void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
+    void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName, boolean killUid) {
         if (isExemptFromExactAlarmPermission(uid)
                 || !isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
             return;
@@ -3805,7 +3827,7 @@
                 && a.windowLength == 0);
         removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
 
-        if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
+        if (killUid && mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
             PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
                     "schedule_exact_alarm revoked");
         }
@@ -4617,6 +4639,7 @@
         public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10;
         public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
         public static final int TARE_AFFORDABILITY_CHANGED = 12;
+        public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;
 
         AlarmHandler() {
             super(Looper.myLooper());
@@ -4715,10 +4738,11 @@
                     break;
 
                 case REMOVE_EXACT_ALARMS:
-                    final int uid = msg.arg1;
-                    final String packageName = (String) msg.obj;
+                    int uid = msg.arg1;
+                    String packageName = (String) msg.obj;
                     synchronized (mLock) {
-                        removeExactAlarmsOnPermissionRevokedLocked(uid, packageName);
+                        removeExactAlarmsOnPermissionRevokedLocked(uid, packageName, /*killUid = */
+                                true);
                     }
                     break;
                 case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED:
@@ -4730,6 +4754,16 @@
                 case REFRESH_EXACT_ALARM_CANDIDATES:
                     refreshExactAlarmCandidates();
                     break;
+                case CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE:
+                    packageName = (String) msg.obj;
+                    uid = msg.arg1;
+                    if (!hasScheduleExactAlarmInternal(packageName, uid)) {
+                        synchronized (mLock) {
+                            removeExactAlarmsOnPermissionRevokedLocked(uid,
+                                    packageName, /*killUid = */false);
+                        }
+                    }
+                    break;
                 default:
                     // nope, just ignore it
                     break;
@@ -4914,6 +4948,12 @@
                         }
                         break;
                     case Intent.ACTION_PACKAGE_ADDED:
+                        if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                            final String packageUpdated = intent.getData().getSchemeSpecificPart();
+                            mHandler.obtainMessage(
+                                    AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE, uid, -1,
+                                    packageUpdated).sendToTarget();
+                        }
                         mHandler.sendEmptyMessage(AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES);
                         return;
                 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index cea1945..e23e067 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -417,6 +417,9 @@
                             break;
                         case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
                         case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
+                        case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC:
+                        case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH:
+                        case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS:
                             mConstants.updateConnectivityConstantsLocked();
                             break;
                         case Constants.KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS:
@@ -489,6 +492,12 @@
         private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
         private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
         private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
+        private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
+                "conn_use_cell_signal_strength";
+        private static final String KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS =
+                "conn_update_all_jobs_min_interval_ms";
+        private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
+                "conn_low_signal_strength_relax_frac";
         private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
                 "prefetch_force_batch_relax_threshold_ms";
         private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
@@ -514,6 +523,9 @@
         private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
+        private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
+        private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS;
+        private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f;
         private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
         private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
         private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
@@ -569,6 +581,23 @@
          * we consider matching it against a metered network.
          */
         public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
+        /**
+         * Whether to use the cell signal strength to determine if a particular job is eligible to
+         * run.
+         */
+        public boolean CONN_USE_CELL_SIGNAL_STRENGTH = DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH;
+        /**
+         * When throttling updating all tracked jobs, make sure not to update them more frequently
+         * than this value.
+         */
+        public long CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS =
+                DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS;
+        /**
+         * The fraction of a job's running window that must pass before we consider running it on
+         * low signal strength networks.
+         */
+        public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
+                DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC;
 
         /**
          * The amount of time within which we would consider the app to be launching relatively soon
@@ -661,6 +690,18 @@
             CONN_PREFETCH_RELAX_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_CONN_PREFETCH_RELAX_FRAC,
                     DEFAULT_CONN_PREFETCH_RELAX_FRAC);
+            CONN_USE_CELL_SIGNAL_STRENGTH = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_CONN_USE_CELL_SIGNAL_STRENGTH,
+                    DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH);
+            CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS,
+                    DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS);
+            CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = DeviceConfig.getFloat(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC,
+                    DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
         }
 
         private void updatePrefetchConstantsLocked() {
@@ -739,6 +780,11 @@
             pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
             pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
             pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
+            pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
+            pw.print(KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS, CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
+                    .println();
+            pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC)
+                    .println();
             pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
                     PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index c678755..892e0c0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -35,6 +35,10 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.UserHandle;
+import android.telephony.CellSignalStrength;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -60,6 +64,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /**
@@ -213,9 +218,16 @@
      * is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
      */
     private final List<UidStats> mSortedStats = new ArrayList<>();
+    @GuardedBy("mLock")
     private long mLastCallbackAdjustmentTimeElapsed;
+    @GuardedBy("mLock")
+    private final SparseArray<CellSignalStrengthCallback> mSignalStrengths = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private long mLastAllJobUpdateTimeElapsed;
 
     private static final int MSG_ADJUST_CALLBACKS = 0;
+    private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
 
     private final Handler mHandler;
 
@@ -529,11 +541,7 @@
     @GuardedBy("mLock")
     public void onBatteryStateChangedLocked() {
         // Update job bookkeeping out of band to avoid blocking broadcast progress.
-        JobSchedulerBackgroundThread.getHandler().post(() -> {
-            synchronized (mLock) {
-                updateTrackedJobsLocked(-1, null);
-            }
-        });
+        mHandler.sendEmptyMessage(MSG_UPDATE_ALL_TRACKED_JOBS);
     }
 
     private boolean isUsable(NetworkCapabilities capabilities) {
@@ -650,6 +658,82 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private boolean isStrongEnough(JobStatus jobStatus, NetworkCapabilities capabilities,
+            Constants constants) {
+        final int priority = jobStatus.getEffectivePriority();
+        if (priority >= JobInfo.PRIORITY_HIGH) {
+            return true;
+        }
+        if (!constants.CONN_USE_CELL_SIGNAL_STRENGTH) {
+            return true;
+        }
+        if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+            return true;
+        }
+        if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
+            // Exclude VPNs because it's currently not possible to determine the VPN's underlying
+            // network, and thus the correct signal strength of the VPN's network.
+            // Transmitting data over a VPN is generally more battery-expensive than on the
+            // underlying network, so:
+            // TODO: find a good way to reduce job use of VPN when it'll be very expensive
+            // For now, we just pretend VPNs are always strong enough
+            return true;
+        }
+
+        // VCNs running over WiFi will declare TRANSPORT_CELLULAR. When connected, a VCN will
+        // most likely be the default network. We ideally don't want this to restrict jobs when the
+        // VCN incorrectly declares the CELLULAR transport, but there's currently no way to
+        // determine if a network is a VCN. When there is:
+        // TODO(216127782): exclude VCN running over WiFi from this check
+
+        int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        // Use the best strength found.
+        final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
+        for (int subId : subscriptionIds) {
+            CellSignalStrengthCallback callback = mSignalStrengths.get(subId);
+            if (callback != null) {
+                signalStrength = Math.max(signalStrength, callback.signalStrength);
+            } else {
+                Slog.wtf(TAG,
+                        "Subscription ID " + subId + " doesn't have a registered callback");
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "Cell signal strength for job=" + signalStrength);
+        }
+        // Treat "NONE_OR_UNKNOWN" as "NONE".
+        if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
+            // If signal strength is poor, don't run MIN or LOW priority jobs, and only
+            // run DEFAULT priority jobs if the device is charging or the job has been waiting
+            // long enough.
+            if (priority > JobInfo.PRIORITY_DEFAULT) {
+                return true;
+            }
+            if (priority < JobInfo.PRIORITY_DEFAULT) {
+                return false;
+            }
+            // DEFAULT job.
+            return (mService.isBatteryCharging() && mService.isBatteryNotLow())
+                    || jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
+        }
+        if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_MODERATE) {
+            // If signal strength is moderate, only run MIN priority jobs when the device
+            // is charging, or the job is already running.
+            if (priority >= JobInfo.PRIORITY_LOW) {
+                return true;
+            }
+            // MIN job.
+            if (mService.isBatteryCharging() && mService.isBatteryNotLow()) {
+                return true;
+            }
+            final UidStats uidStats = getUidStats(
+                    jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
+            return uidStats.runningJobs.contains(jobStatus);
+        }
+        return true;
+    }
+
     private static NetworkCapabilities.Builder copyCapabilities(
             @NonNull final NetworkRequest request) {
         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
@@ -717,10 +801,12 @@
         // Second, is the network congested?
         if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
 
-        // Third, is the network a strict match?
+        if (!isStrongEnough(jobStatus, capabilities, constants)) return false;
+
+        // Is the network a strict match?
         if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;
 
-        // Third, is the network a relaxed match?
+        // Is the network a relaxed match?
         if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;
 
         return false;
@@ -985,6 +1071,24 @@
         return changed;
     }
 
+    @GuardedBy("mLock")
+    private void updateAllTrackedJobsLocked(boolean allowThrottle) {
+        if (allowThrottle) {
+            final long throttleTimeLeftMs =
+                    (mLastAllJobUpdateTimeElapsed + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
+                            - sElapsedRealtimeClock.millis();
+            if (throttleTimeLeftMs > 0) {
+                Message msg = mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0);
+                mHandler.sendMessageDelayed(msg, throttleTimeLeftMs);
+                return;
+            }
+        }
+
+        mHandler.removeMessages(MSG_UPDATE_ALL_TRACKED_JOBS);
+        updateTrackedJobsLocked(-1, null);
+        mLastAllJobUpdateTimeElapsed = sElapsedRealtimeClock.millis();
+    }
+
     /**
      * Update any jobs tracked by this controller that match given filters.
      *
@@ -1088,7 +1192,11 @@
                 Slog.v(TAG, "onCapabilitiesChanged: " + network);
             }
             synchronized (mLock) {
-                mAvailableNetworks.put(network, capabilities);
+                final NetworkCapabilities oldCaps = mAvailableNetworks.put(network, capabilities);
+                if (oldCaps != null) {
+                    maybeUnregisterSignalStrengthCallbackLocked(oldCaps);
+                }
+                maybeRegisterSignalStrengthCallbackLocked(capabilities);
                 updateTrackedJobsLocked(-1, network);
                 postAdjustCallbacks();
             }
@@ -1100,7 +1208,10 @@
                 Slog.v(TAG, "onLost: " + network);
             }
             synchronized (mLock) {
-                mAvailableNetworks.remove(network);
+                final NetworkCapabilities capabilities = mAvailableNetworks.remove(network);
+                if (capabilities != null) {
+                    maybeUnregisterSignalStrengthCallbackLocked(capabilities);
+                }
                 for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) {
                     UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u);
                     if (Objects.equals(callback.mDefaultNetwork, network)) {
@@ -1111,6 +1222,63 @@
                 postAdjustCallbacks();
             }
         }
+
+        @GuardedBy("mLock")
+        private void maybeRegisterSignalStrengthCallbackLocked(
+                @NonNull NetworkCapabilities capabilities) {
+            if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                return;
+            }
+            TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+            final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
+            for (int subId : subscriptionIds) {
+                if (mSignalStrengths.indexOfKey(subId) >= 0) {
+                    continue;
+                }
+                TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
+                CellSignalStrengthCallback callback = new CellSignalStrengthCallback();
+                idTm.registerTelephonyCallback(
+                        JobSchedulerBackgroundThread.getExecutor(), callback);
+                mSignalStrengths.put(subId, callback);
+
+                final SignalStrength signalStrength = idTm.getSignalStrength();
+                if (signalStrength != null) {
+                    callback.signalStrength = signalStrength.getLevel();
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void maybeUnregisterSignalStrengthCallbackLocked(
+                @NonNull NetworkCapabilities capabilities) {
+            if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                return;
+            }
+            ArraySet<Integer> activeIds = new ArraySet<>();
+            for (int i = 0, size = mAvailableNetworks.size(); i < size; ++i) {
+                NetworkCapabilities nc = mAvailableNetworks.valueAt(i);
+                if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                    activeIds.addAll(nc.getSubscriptionIds());
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Active subscription IDs: " + activeIds);
+            }
+            TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+            Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
+            for (int subId : subscriptionIds) {
+                if (activeIds.contains(subId)) {
+                    continue;
+                }
+                TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
+                CellSignalStrengthCallback callback = mSignalStrengths.removeReturnOld(subId);
+                if (callback != null) {
+                    idTm.unregisterTelephonyCallback(callback);
+                } else {
+                    Slog.wtf(TAG, "Callback for sub " + subId + " didn't exist?!?!");
+                }
+            }
+        }
     };
 
     private class CcHandler extends Handler {
@@ -1127,6 +1295,13 @@
                             maybeAdjustRegisteredCallbacksLocked();
                         }
                         break;
+
+                    case MSG_UPDATE_ALL_TRACKED_JOBS:
+                        synchronized (mLock) {
+                            final boolean allowThrottle = msg.arg1 == 1;
+                            updateAllTrackedJobsLocked(allowThrottle);
+                        }
+                        break;
                 }
             }
         }
@@ -1268,6 +1443,33 @@
         }
     }
 
+    private class CellSignalStrengthCallback extends TelephonyCallback
+            implements TelephonyCallback.SignalStrengthsListener {
+        @GuardedBy("mLock")
+        public int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_GREAT;
+
+        @Override
+        public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
+            synchronized (mLock) {
+                final int newSignalStrength = signalStrength.getLevel();
+                if (DEBUG) {
+                    Slog.d(TAG, "Signal strength changing from "
+                            + this.signalStrength + " to " + newSignalStrength);
+                    for (CellSignalStrength css : signalStrength.getCellSignalStrengths()) {
+                        Slog.d(TAG, "CSS: " + css.getLevel() + " " + css);
+                    }
+                }
+                if (this.signalStrength == newSignalStrength) {
+                    // This happens a lot.
+                    return;
+                }
+                this.signalStrength = newSignalStrength;
+                // Update job bookkeeping out of band to avoid blocking callback progress.
+                mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0).sendToTarget();
+            }
+        }
+    }
+
     @GuardedBy("mLock")
     @Override
     public void dumpControllerStateLocked(IndentingPrintWriter pw,
@@ -1299,6 +1501,20 @@
         }
         pw.println();
 
+        if (mSignalStrengths.size() > 0) {
+            pw.println("Subscription ID signal strengths:");
+            pw.increaseIndent();
+            for (int i = 0; i < mSignalStrengths.size(); ++i) {
+                pw.print(mSignalStrengths.keyAt(i));
+                pw.print(": ");
+                pw.println(mSignalStrengths.valueAt(i).signalStrength);
+            }
+            pw.decreaseIndent();
+        } else {
+            pw.println("No cached signal strengths");
+        }
+        pw.println();
+
         pw.println("Current default network callbacks:");
         pw.increaseIndent();
         for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 0456a9b..acbb1c1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1139,11 +1139,12 @@
      */
     public float getFractionRunTime() {
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
+        if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME
+                && latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
             return 1;
-        } else if (earliestRunTimeElapsedMillis == 0) {
+        } else if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) {
             return now >= latestRunTimeElapsedMillis ? 1 : 0;
-        } else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
+        } else if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
             return now >= earliestRunTimeElapsedMillis ? 1 : 0;
         } else {
             if (now <= earliestRunTimeElapsedMillis) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 5ee3e1d..77f869a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -112,6 +112,7 @@
     field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
     field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
     field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
+    field public static final String MANAGE_WIFI_INTERFACES = "android.permission.MANAGE_WIFI_INTERFACES";
     field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
     field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
     field public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
@@ -194,6 +195,7 @@
     field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
     field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION";
     field public static final String USE_BIOMETRIC = "android.permission.USE_BIOMETRIC";
+    field public static final String USE_EXACT_ALARM = "android.permission.USE_EXACT_ALARM";
     field @Deprecated public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
     field public static final String USE_FULL_SCREEN_INTENT = "android.permission.USE_FULL_SCREEN_INTENT";
     field public static final String USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER";
@@ -341,6 +343,7 @@
     field public static final int allowSingleTap = 16843353; // 0x1010259
     field public static final int allowTaskReparenting = 16843268; // 0x1010204
     field public static final int allowUndo = 16843999; // 0x10104df
+    field public static final int allowUntrustedActivityEmbedding;
     field public static final int alpha = 16843551; // 0x101031f
     field public static final int alphabeticModifiers = 16844110; // 0x101054e
     field public static final int alphabeticShortcut = 16843235; // 0x10101e3
@@ -905,6 +908,7 @@
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
     field public static final int killAfterRestore = 16843420; // 0x101029c
+    field public static final int knownActivityEmbeddingCerts;
     field public static final int knownCerts = 16844330; // 0x101062a
     field public static final int lStar = 16844359; // 0x1010647
     field public static final int label = 16842753; // 0x1010001
@@ -1747,6 +1751,7 @@
     field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d
     field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e
     field public static final int windowSplashScreenBackground = 16844332; // 0x101062c
+    field public static final int windowSplashScreenBehavior;
     field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f
     field public static final int windowSplashScreenIconBackgroundColor = 16844336; // 0x1010630
     field @Deprecated public static final int windowSplashscreenContent = 16844132; // 0x1010564
@@ -3066,6 +3071,7 @@
     method @Nullable public final android.accessibilityservice.InputMethod getInputMethod();
     method @NonNull public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
+    method @Nullable public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(int);
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
     method @NonNull public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
     method @NonNull public final java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
@@ -4533,6 +4539,7 @@
     method @Nullable public android.graphics.Rect getLaunchBounds();
     method public int getLaunchDisplayId();
     method public boolean getLockTaskMode();
+    method public int getSplashScreenStyle();
     method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
@@ -10964,6 +10971,7 @@
     ctor public ActivityInfo(android.content.pm.ActivityInfo);
     method public int describeContents();
     method public void dump(android.util.Printer, String);
+    method @NonNull public java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts();
     method public final int getThemeResource();
     field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
     field public static final int COLOR_MODE_HDR = 2; // 0x2
@@ -10991,6 +10999,7 @@
     field public static final int DOCUMENT_LAUNCH_NEVER = 3; // 0x3
     field public static final int DOCUMENT_LAUNCH_NONE = 0; // 0x0
     field public static final int FLAG_ALLOW_TASK_REPARENTING = 64; // 0x40
+    field public static final int FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING = 268435456; // 0x10000000
     field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
     field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
     field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4
@@ -11080,6 +11089,7 @@
     method public void dump(android.util.Printer, String);
     method public static CharSequence getCategoryTitle(android.content.Context, int);
     method public int getGwpAsanMode();
+    method @NonNull public java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts();
     method public int getMemtagMode();
     method public int getNativeHeapZeroInitialized();
     method public int getRequestRawExternalStorageAccess();
@@ -18195,6 +18205,7 @@
     method public void enableSurfaceSharing();
     method public int getDynamicRangeProfile();
     method public int getMaxSharedSurfaceCount();
+    method public int getMirrorMode();
     method public int getStreamUseCase();
     method @Nullable public android.view.Surface getSurface();
     method public int getSurfaceGroupId();
@@ -18203,11 +18214,16 @@
     method public void removeSensorPixelModeUsed(int);
     method public void removeSurface(@NonNull android.view.Surface);
     method public void setDynamicRangeProfile(int);
+    method public void setMirrorMode(int);
     method public void setPhysicalCameraId(@Nullable String);
     method public void setStreamUseCase(int);
     method public void setTimestampBase(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
+    field public static final int MIRROR_MODE_AUTO = 0; // 0x0
+    field public static final int MIRROR_MODE_H = 2; // 0x2
+    field public static final int MIRROR_MODE_NONE = 1; // 0x1
+    field public static final int MIRROR_MODE_V = 3; // 0x3
     field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
     field public static final int TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED = 4; // 0x4
     field public static final int TIMESTAMP_BASE_DEFAULT = 0; // 0x0
@@ -26469,7 +26485,7 @@
     method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthPsk(@NonNull byte[]);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthUsernamePassword(@NonNull String, @NonNull String, @Nullable java.security.cert.X509Certificate);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setBypassable(boolean);
-    method @NonNull public android.net.Ikev2VpnProfile.Builder setExcludeLocalRoutes(boolean);
+    method @NonNull public android.net.Ikev2VpnProfile.Builder setLocalRoutesExcluded(boolean);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setMaxMtu(int);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setMetered(boolean);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo);
@@ -26547,7 +26563,7 @@
   }
 
   public abstract class PlatformVpnProfile {
-    method public final boolean getExcludeLocalRoutes();
+    method public final boolean areLocalRoutesExcluded();
     method public final boolean getRequiresInternetValidation();
     method public final int getType();
     method @NonNull public final String getTypeString();
@@ -26744,6 +26760,23 @@
     method @Deprecated public void startProvisionedVpnProfile();
     method @NonNull public String startProvisionedVpnProfileSession();
     method public void stopProvisionedVpnProfile();
+    field public static final String ACTION_VPN_MANAGER_EVENT = "android.net.action.VPN_MANAGER_EVENT";
+    field public static final String CATEGORY_EVENT_DEACTIVATED_BY_USER = "android.net.category.EVENT_DEACTIVATED_BY_USER";
+    field public static final String CATEGORY_EVENT_IKE_ERROR = "android.net.category.EVENT_IKE_ERROR";
+    field public static final String CATEGORY_EVENT_NETWORK_ERROR = "android.net.category.EVENT_NETWORK_ERROR";
+    field public static final int ERROR_CLASS_NOT_RECOVERABLE = 1; // 0x1
+    field public static final int ERROR_CLASS_RECOVERABLE = 2; // 0x2
+    field public static final int ERROR_CODE_NETWORK_IO = 3; // 0x3
+    field public static final int ERROR_CODE_NETWORK_LOST = 2; // 0x2
+    field public static final int ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT = 1; // 0x1
+    field public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0; // 0x0
+    field public static final String EXTRA_ERROR_CLASS = "android.net.extra.ERROR_CLASS";
+    field public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE";
+    field public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY";
+    field public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP";
+    field public static final String EXTRA_UNDERLYING_LINK_PROPERTIES = "android.net.extra.UNDERLYING_LINK_PROPERTIES";
+    field public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK";
+    field public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES = "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES";
   }
 
   public class VpnService extends android.app.Service {
@@ -41338,13 +41371,13 @@
     field public static final int IPSEC_ENCRYPTION_ALGORITHM_AES_CBC = 2; // 0x2
     field public static final int IPSEC_ENCRYPTION_ALGORITHM_DES_EDE3_CBC = 1; // 0x1
     field public static final int IPSEC_ENCRYPTION_ALGORITHM_NULL = 0; // 0x0
-    field public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = "ims.key_capability_type_call_composer_int_array";
-    field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.key_capability_type_options_uce_int_array";
-    field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.key_capability_type_presence_uce_int_array";
-    field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.key_capability_type_sms_int_array";
-    field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.key_capability_type_ut_int_array";
-    field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.key_capability_type_video_int_array";
-    field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.key_capability_type_voice_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY = "ims.capability_type_call_composer_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.capability_type_options_uce_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.capability_type_presence_uce_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.capability_type_sms_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.capability_type_ut_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.capability_type_video_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.capability_type_voice_int_array";
     field public static final String KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL = "ims.enable_presence_capability_exchange_bool";
     field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool";
     field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool";
@@ -51600,6 +51633,7 @@
     method @Deprecated public void getBoundsInParent(android.graphics.Rect);
     method public void getBoundsInScreen(android.graphics.Rect);
     method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
+    method @Nullable public android.view.accessibility.AccessibilityNodeInfo getChild(int, int);
     method public int getChildCount();
     method public CharSequence getClassName();
     method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo();
@@ -51619,6 +51653,7 @@
     method public CharSequence getPackageName();
     method @Nullable public CharSequence getPaneTitle();
     method public android.view.accessibility.AccessibilityNodeInfo getParent();
+    method @Nullable public android.view.accessibility.AccessibilityNodeInfo getParent(int);
     method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
     method @Nullable public CharSequence getStateDescription();
     method public CharSequence getText();
@@ -51769,8 +51804,15 @@
     field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20
     field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
     field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+    field public static final int FLAG_PREFETCH_ANCESTORS = 1; // 0x1
+    field public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 16; // 0x10
+    field public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 8; // 0x8
+    field public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 4; // 0x4
+    field public static final int FLAG_PREFETCH_SIBLINGS = 2; // 0x2
+    field public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 32; // 0x20
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
+    field public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50; // 0x32
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_LINE = 4; // 0x4
     field public static final int MOVEMENT_GRANULARITY_PAGE = 16; // 0x10
@@ -51935,6 +51977,7 @@
     method public int getScrollX();
     method public int getScrollY();
     method @Nullable public android.view.accessibility.AccessibilityNodeInfo getSource();
+    method @Nullable public android.view.accessibility.AccessibilityNodeInfo getSource(int);
     method @NonNull public java.util.List<java.lang.CharSequence> getText();
     method public int getToIndex();
     method public int getWindowId();
@@ -51992,6 +52035,7 @@
     method public android.view.accessibility.AccessibilityWindowInfo getParent();
     method public void getRegionInScreen(@NonNull android.graphics.Region);
     method public android.view.accessibility.AccessibilityNodeInfo getRoot();
+    method @Nullable public android.view.accessibility.AccessibilityNodeInfo getRoot(int);
     method @Nullable public CharSequence getTitle();
     method public int getType();
     method public boolean isAccessibilityFocused();
@@ -52376,6 +52420,8 @@
     method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
     method public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
     method public void setUserData(@Nullable android.service.autofill.UserData);
+    method public boolean showAutofillDialog(@NonNull android.view.View);
+    method public boolean showAutofillDialog(@NonNull android.view.View, int);
     method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
     field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 5df593d..24b4f89 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -250,6 +250,22 @@
 
 package android.net {
 
+  public class EthernetManager {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener);
+    method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener);
+    method public void setIncludeTestInterfaces(boolean);
+    field public static final int ROLE_CLIENT = 1; // 0x1
+    field public static final int ROLE_NONE = 0; // 0x0
+    field public static final int ROLE_SERVER = 2; // 0x2
+    field public static final int STATE_ABSENT = 0; // 0x0
+    field public static final int STATE_LINK_DOWN = 1; // 0x1
+    field public static final int STATE_LINK_UP = 2; // 0x2
+  }
+
+  public static interface EthernetManager.InterfaceStateListener {
+    method public void onInterfaceStateChanged(@NonNull String, int, int, @Nullable android.net.IpConfiguration);
+  }
+
   public class LocalSocket implements java.io.Closeable {
     ctor public LocalSocket(@NonNull java.io.FileDescriptor);
   }
@@ -466,6 +482,25 @@
     method public static int logToRadioBuffer(int, @Nullable String, @Nullable String);
   }
 
+  public final class Slog {
+    method public static int d(@Nullable String, @NonNull String);
+    method public static int d(@Nullable String, @NonNull String, @Nullable Throwable);
+    method public static int e(@Nullable String, @NonNull String);
+    method public static int e(@Nullable String, @NonNull String, @Nullable Throwable);
+    method public static int i(@Nullable String, @NonNull String);
+    method public static int i(@Nullable String, @NonNull String, @Nullable Throwable);
+    method public static int v(@Nullable String, @NonNull String);
+    method public static int v(@Nullable String, @NonNull String, @Nullable Throwable);
+    method public static int w(@Nullable String, @NonNull String);
+    method public static int w(@Nullable String, @NonNull String, @Nullable Throwable);
+    method public static int w(@Nullable String, @Nullable Throwable);
+    method public static int wtf(@Nullable String, @NonNull String);
+    method public static int wtf(@Nullable String, @Nullable Throwable);
+    method public static int wtf(@Nullable String, @NonNull String, @Nullable Throwable);
+    method public static void wtfQuiet(@Nullable String, @NonNull String);
+    method public static int wtfStack(@Nullable String, @NonNull String);
+  }
+
   public class SystemConfigFileCommitEventLogger {
     ctor public SystemConfigFileCommitEventLogger(@NonNull String);
     method public void setStartTime(long);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 82539e8..179c508 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -851,6 +851,10 @@
     field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
   }
 
+  public static class Notification.MediaStyle extends android.app.Notification.Style {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent);
+  }
+
   public static final class Notification.TvExtender implements android.app.Notification.Extender {
     ctor public Notification.TvExtender();
     ctor public Notification.TvExtender(android.app.Notification);
@@ -914,7 +918,7 @@
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarModeOverride();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setNavBarModeOverride(int);
-    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable android.graphics.drawable.Icon, @Nullable CharSequence);
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferSenderDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
     field public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; // 0x0
     field public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1; // 0x1
@@ -1643,6 +1647,7 @@
     method public int getResultNumber();
     method public int getResultOffset();
     method @NonNull public android.os.Bundle getSearchConstraints();
+    method @NonNull public String getSource();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "IS_PRESUBMIT_SUGGESTION";
     field public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "SEARCH_PROVIDER_FILTER";
@@ -2750,6 +2755,7 @@
     method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener, @NonNull java.util.concurrent.Executor);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
     method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(int, int, int, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
@@ -2782,6 +2788,39 @@
 
 }
 
+package android.companion.virtual.audio {
+
+  public final class AudioCapture {
+    ctor public AudioCapture();
+    method public int getRecordingState();
+    method public int read(@NonNull java.nio.ByteBuffer, int);
+    method public void startRecording();
+    method public void stop();
+  }
+
+  public final class AudioInjection {
+    ctor public AudioInjection();
+    method public int getPlayState();
+    method public void play();
+    method public void stop();
+    method public int write(@NonNull java.nio.ByteBuffer, int, int);
+  }
+
+  public final class VirtualAudioDevice implements java.io.Closeable {
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void close();
+    method @Nullable public android.companion.virtual.audio.AudioCapture getAudioCapture();
+    method @Nullable public android.companion.virtual.audio.AudioInjection getAudioInjection();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.companion.virtual.audio.AudioCapture startAudioCapture(@NonNull android.media.AudioFormat);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.companion.virtual.audio.AudioInjection startAudioInjection(@NonNull android.media.AudioFormat);
+  }
+
+  public static interface VirtualAudioDevice.AudioConfigurationChangeCallback {
+    method public void onPlaybackConfigChanged(@NonNull java.util.List<android.media.AudioPlaybackConfiguration>);
+    method public void onRecordingConfigChanged(@NonNull java.util.List<android.media.AudioRecordingConfiguration>);
+  }
+
+}
+
 package android.content {
 
   public class ApexEnvironment {
@@ -2993,6 +3032,7 @@
   }
 
   public static final class IntegrityFormula.Application {
+    method @NonNull public static android.content.integrity.IntegrityFormula certificateLineageContains(@NonNull String);
     method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String);
     method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled();
     method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String);
@@ -3287,6 +3327,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
     field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
     field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+    field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
     field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
@@ -5136,6 +5177,15 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.Keyphrase> CREATOR;
   }
 
+  public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getCoarseConfidenceLevel();
+    method public int getKeyphraseId();
+    method public int getRecognitionModes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> CREATOR;
+  }
+
   public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
     ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
     ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
@@ -5986,6 +6036,7 @@
 
   public class AudioManager {
     method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addAssistantServicesUids(@NonNull java.util.List<java.lang.Integer>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
@@ -5993,7 +6044,9 @@
     method public void clearAudioServerStateCallback();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<java.lang.Integer> getActiveAssistantServicesUids();
     method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<java.lang.Integer> getAssistantServicesUids();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
@@ -6018,6 +6071,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull java.util.List<java.lang.Integer>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener);
@@ -6025,6 +6079,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setActiveAssistantServiceUids(@NonNull java.util.List<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
@@ -6160,6 +6215,10 @@
     method @NonNull public android.media.HwAudioSource.Builder setAudioDeviceInfo(@NonNull android.media.AudioDeviceInfo);
   }
 
+  public final class MediaCodec {
+    method @NonNull @RequiresPermission("android.permission.MEDIA_RESOURCE_OVERRIDE_PID") public static android.media.MediaCodec createByCodecNameForClient(@NonNull String, int, int) throws java.io.IOException;
+  }
+
   public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
     method @RequiresPermission(android.Manifest.permission.BIND_IMS_SERVICE) public void setOnRtpRxNoticeListener(@NonNull android.content.Context, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaPlayer.OnRtpRxNoticeListener);
   }
@@ -6656,7 +6715,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String);
-    method public int getClientPriority(int, @Nullable String);
+    method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @Nullable String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
     method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
     method @Nullable @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String);
@@ -8856,7 +8915,7 @@
     method @Nullable public android.net.wifi.nl80211.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
     method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int);
     method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
-    method public boolean notifyCountryCodeChanged();
+    method public void notifyCountryCodeChanged(@Nullable String);
     method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
     method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback);
     method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener);
@@ -11091,7 +11150,8 @@
     method public int encodeSmdxSubjectAndReasonCode(@Nullable String, @Nullable String);
     method @CallSuper public android.os.IBinder onBind(android.content.Intent);
     method public abstract int onDeleteSubscription(int, String);
-    method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle);
+    method @Deprecated public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle);
+    method @NonNull public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @NonNull android.os.Bundle);
     method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean);
     method @Deprecated public abstract int onEraseSubscriptions(int);
     method public int onEraseSubscriptions(int, @android.telephony.euicc.EuiccCardManager.ResetOption int);
@@ -11710,8 +11770,13 @@
   public static class AlwaysOnHotwordDetector.EventPayload {
     method @Nullable public android.os.ParcelFileDescriptor getAudioStream();
     method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
+    method @Nullable public byte[] getData();
+    method public int getDataFormat();
     method @Nullable public android.service.voice.HotwordDetectedResult getHotwordDetectedResult();
-    method @Nullable public byte[] getTriggerAudio();
+    method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras();
+    method @Deprecated @Nullable public byte[] getTriggerAudio();
+    field public static final int DATA_FORMAT_RAW = 0; // 0x0
+    field public static final int DATA_FORMAT_TRIGGER_AUDIO = 1; // 0x1
   }
 
   public static final class AlwaysOnHotwordDetector.ModelParamRange {
@@ -11782,6 +11847,7 @@
   }
 
   public interface HotwordDetector {
+    method public default void destroy();
     method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
     method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
     method public boolean stopRecognition();
@@ -13831,7 +13897,7 @@
     field public static final int RESULT_CALLER_NOT_ALLOWED = -3; // 0xfffffffd
     field public static final int RESULT_EUICC_NOT_FOUND = -2; // 0xfffffffe
     field public static final int RESULT_OK = 0; // 0x0
-    field public static final int RESULT_PROFILE_NOT_FOUND = -4; // 0xfffffffc
+    field public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1
     field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bcc7e4a..9634252 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -300,6 +300,9 @@
 
   public class Notification implements android.os.Parcelable {
     method public boolean shouldShowForegroundImmediately();
+    field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
+    field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
+    field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
   }
 
   public final class NotificationChannel implements android.os.Parcelable {
@@ -483,6 +486,7 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void acknowledgeNewUserDisclaimer();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId();
     method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
@@ -601,6 +605,14 @@
 
 }
 
+package android.app.cloudsearch {
+
+  public static final class SearchRequest.Builder {
+    method @NonNull public android.app.cloudsearch.SearchRequest.Builder setSource(@NonNull String);
+  }
+
+}
+
 package android.app.contentsuggestions {
 
   public final class ContentSuggestionsManager {
@@ -1276,6 +1288,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
   }
 
+  public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
+    ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
+  }
+
   public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
     ctor public SoundTrigger.ModelParamRange(int, int);
   }
@@ -1607,10 +1623,6 @@
 
 package android.net {
 
-  public class EthernetManager {
-    method public void setIncludeTestInterfaces(boolean);
-  }
-
   public class NetworkPolicyManager {
     method public boolean getRestrictBackground();
     method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean);
@@ -1753,6 +1765,7 @@
 
   public class Process {
     method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException;
+    method public static final int toSupplementalUid(int);
     field public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; // 0x15f90
     field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8
     field public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; // 0x182b7
@@ -2064,7 +2077,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
     method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
-    method public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL) public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int);
   }
 
 }
@@ -2401,6 +2414,19 @@
     method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]);
   }
 
+  public static final class AlwaysOnHotwordDetector.EventPayload.Builder {
+    ctor public AlwaysOnHotwordDetector.EventPayload.Builder();
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload build();
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setAudioStream(@NonNull android.os.ParcelFileDescriptor);
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureAudioFormat(@NonNull android.media.AudioFormat);
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureAvailable(boolean);
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureSession(int);
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setData(@NonNull byte[]);
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setDataFormat(int);
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setHotwordDetectedResult(@NonNull android.service.voice.HotwordDetectedResult);
+    method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+  }
+
   public final class VisibleActivityInfo implements android.os.Parcelable {
     ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
   }
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index cf3ca20..c82f5f6 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1008,14 +1008,6 @@
      * is currently touching or the window with input focus, if the user is not
      * touching any window. It could be from any logical display.
      * <p>
-     * The currently active window is defined as the window that most recently fired one
-     * of the following events:
-     * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED},
-     * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
-     * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}.
-     * In other words, the last window shown that also has input focus.
-     * </p>
-     * <p>
      * <strong>Note:</strong> In order to access the root node your service has
      * to declare the capability to retrieve window content by setting the
      * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
@@ -1023,10 +1015,29 @@
      * </p>
      *
      * @return The root node if this service can retrieve window content.
+     * @see AccessibilityWindowInfo#isActive() for more explanation about the active window.
      */
     public AccessibilityNodeInfo getRootInActiveWindow() {
+        return getRootInActiveWindow(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
+    }
+
+    /**
+     * Gets the root node in the currently active window if this service
+     * can retrieve window content. The active window is the one that the user
+     * is currently touching or the window with input focus, if the user is not
+     * touching any window. It could be from any logical display.
+     *
+     * @param prefetchingStrategy the prefetching strategy.
+     * @return The root node if this service can retrieve window content.
+     *
+     * @see #getRootInActiveWindow()
+     * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+     */
+    @Nullable
+    public AccessibilityNodeInfo getRootInActiveWindow(
+            @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
         return AccessibilityInteractionClient.getInstance(this).getRootInActiveWindow(
-                mConnectionId);
+                mConnectionId, prefetchingStrategy);
     }
 
     /**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a31aa28..983dde3 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -47,7 +47,9 @@
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ActivityNotFoundException;
+import android.content.ComponentCallbacks;
 import android.content.ComponentCallbacks2;
+import android.content.ComponentCallbacksController;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -982,6 +984,8 @@
     @Nullable
     private DumpableContainerImpl mDumpableContainer;
 
+    private ComponentCallbacksController mCallbacksController;
+
     private final WindowControllerCallback mWindowControllerCallback =
             new WindowControllerCallback() {
         /**
@@ -1323,6 +1327,28 @@
         }
     }
 
+    @Override
+    public void registerComponentCallbacks(ComponentCallbacks callback) {
+        if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)
+                && mCallbacksController == null) {
+            mCallbacksController = new ComponentCallbacksController();
+        }
+        if (mCallbacksController != null) {
+            mCallbacksController.registerCallbacks(callback);
+        } else {
+            super.registerComponentCallbacks(callback);
+        }
+    }
+
+    @Override
+    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+        if (mCallbacksController != null) {
+            mCallbacksController.unregisterCallbacks(callback);
+        } else {
+            super.unregisterComponentCallbacks(callback);
+        }
+    }
+
     private void dispatchActivityPreCreated(@Nullable Bundle savedInstanceState) {
         getApplication().dispatchActivityPreCreated(this, savedInstanceState);
         Object[] callbacks = collectActivityLifecycleCallbacks();
@@ -2668,9 +2694,12 @@
         if (mUiTranslationController != null) {
             mUiTranslationController.onActivityDestroyed();
         }
-
         if (mDefaultBackCallback != null) {
             getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
+            mDefaultBackCallback = null;
+        }
+        if (mCallbacksController != null) {
+            mCallbacksController.clearCallbacks();
         }
     }
 
@@ -2991,6 +3020,9 @@
         }
 
         dispatchActivityConfigurationChanged();
+        if (mCallbacksController != null) {
+            mCallbacksController.dispatchConfigurationChanged(newConfig);
+        }
     }
 
     /**
@@ -3162,12 +3194,18 @@
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this);
         mCalled = true;
         mFragments.dispatchLowMemory();
+        if (mCallbacksController != null) {
+            mCallbacksController.dispatchLowMemory();
+        }
     }
 
     public void onTrimMemory(int level) {
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
         mCalled = true;
         mFragments.dispatchTrimMemory(level);
+        if (mCallbacksController != null) {
+            mCallbacksController.dispatchTrimMemory(level);
+        }
     }
 
     /**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index cce7dd3..a58ceaa 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -215,6 +215,14 @@
     public abstract boolean isSystemReady();
 
     /**
+     * Returns package name given pid.
+     *
+     * @param pid The pid we are searching package name for.
+     */
+    @Nullable
+    public abstract String getPackageNameByPid(int pid);
+
+    /**
      * Sets if the given pid has an overlay UI or not.
      *
      * @param pid The pid we are setting overlay UI for.
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index e405b60..acbcb3e 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1429,7 +1429,6 @@
     /**
      * Gets the style can be used for cold-launching an activity.
      * @see #setSplashScreenStyle(int)
-     * @hide
      */
     public @SplashScreen.SplashScreenStyle int getSplashScreenStyle() {
         return mSplashScreenStyle;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5b8969e..61d1865 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2132,7 +2132,16 @@
                     Looper.myLooper().quit();
                     break;
                 case RECEIVER:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
+                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                        ReceiverData rec = (ReceiverData) msg.obj;
+                        if (rec.intent != null) {
+                            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                                    "broadcastReceiveComp: " + rec.intent.getAction());
+                        } else {
+                            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                                    "broadcastReceiveComp");
+                        }
+                    }
                     handleReceiver((ReceiverData)msg.obj);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 2084775..aa6c184 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -465,6 +465,7 @@
                 }
             };
             getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
+            mDefaultBackCallback = null;
         }
     }
 
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index 9fea3f7..cadbf23 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -140,7 +140,7 @@
  *      embed}
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices
+ *      {@link androidx.fragment.app.DialogFragment} for consistent behavior across all devices
  *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index 536c866..ff9dbcb 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -26,7 +26,7 @@
  * Callbacks to a {@link Fragment}'s container.
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.FragmentContainer}.
+ *      {@link androidx.fragment.app.FragmentContainer}.
  */
 @Deprecated
 public abstract class FragmentContainer {
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 150b7a5..3c8d760 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -41,7 +41,7 @@
  * The methods provided by {@link FragmentController} are for that purpose.
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.FragmentController}
+ *      {@link androidx.fragment.app.FragmentController}
  */
 @Deprecated
 public class FragmentController {
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 9e887b8..6cfdc53 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -40,7 +40,7 @@
  * applicable to the host.
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.FragmentHostCallback}
+ *      {@link androidx.fragment.app.FragmentHostCallback}
  */
 @Deprecated
 public abstract class FragmentHostCallback<E> extends FragmentContainer {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 5435558..f8f846d 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -72,12 +72,12 @@
  * While the FragmentManager API was introduced in
  * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
  * at is also available for use on older platforms through
- * {@link android.support.v4.app.FragmentActivity}.  See the blog post
+ * {@link androidx.fragment.app.FragmentActivity}.  See the blog post
  * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
  * Fragments For All</a> for more details.
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices
+ *      {@link androidx.fragment.app.FragmentManager} for consistent behavior across all devices
  *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
@@ -94,7 +94,7 @@
      * will be persisted across activity instances.
      *
      * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
-     *      Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry}
+     *      Support Library</a> {@link androidx.fragment.app.FragmentManager.BackStackEntry}
      */
     @Deprecated
     public interface BackStackEntry {
@@ -142,7 +142,7 @@
      *
      * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
      *      Support Library</a>
-     *      {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
+     *      {@link androidx.fragment.app.FragmentManager.OnBackStackChangedListener}
      */
     @Deprecated
     public interface OnBackStackChangedListener {
@@ -446,7 +446,7 @@
      *
      * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
      *      Support Library</a>
-     *      {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
+     *      {@link androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks}
      */
     @Deprecated
     public abstract static class FragmentLifecycleCallbacks {
diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java
index 326438a..ae7fd64 100644
--- a/core/java/android/app/FragmentManagerNonConfig.java
+++ b/core/java/android/app/FragmentManagerNonConfig.java
@@ -29,7 +29,7 @@
  * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.FragmentManagerNonConfig}
+ *      {@link androidx.fragment.app.FragmentManagerNonConfig}
  */
 @Deprecated
 public class FragmentManagerNonConfig {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 713a559..34c4928 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -23,7 +23,7 @@
  * </div>
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.FragmentTransaction}
+ *      {@link androidx.fragment.app.FragmentTransaction}
  */
 @Deprecated
 public abstract class FragmentTransaction {
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index c5add66..cc8b182 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -130,6 +130,7 @@
             in ProfilerInfo profilerInfo, in Bundle options, int userId);
     int startAssistantActivity(in String callingPackage, in String callingFeatureId, int callingPid,
             int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)")
     int startActivityFromGameSession(IApplicationThread caller, in String callingPackage,
             in String callingFeatureId, int callingPid, int callingUid, in Intent intent,
             int taskId, int userId);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index a74438a..e0c69df 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -381,10 +381,6 @@
      * Force the global system in or out of touch mode. This can be used if your
      * instrumentation relies on the UI being in one more or the other when it starts.
      *
-     * <p><b>Note:</b> Starting from Android {@link Build.VERSION_CODES#TIRAMISU}, this method
-     * will only have an effect if the calling process is also the focused window owner or has
-     * {@link android.permission#MODIFY_TOUCH_MODE_STATE} permission granted.
-     *
      * @param inTouch Set to true to be in touch mode, false to be in focus mode.
      */
     public void setInTouchMode(boolean inTouch) {
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 5de0d69..2e83308 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -52,7 +52,7 @@
  * guide.</p>
  * </div>
  *
- * @see android.support.v4.app.JobIntentService
+ * @see androidx.core.app.JobIntentService
  *
  * @deprecated IntentService is subject to all the
  *   <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a>
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 7790f70..b6d80ca 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -146,7 +146,7 @@
  * @see android.widget.ListView
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.ListFragment} for consistent behavior across all devices
+ *      {@link androidx.fragment.app.ListFragment} for consistent behavior across all devices
  *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 38e344e..77c7c6f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1734,7 +1734,11 @@
                         return;
                     }
 
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg");
+                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                                "broadcastReceiveReg: " + intent.getAction());
+                    }
+
                     try {
                         ClassLoader cl = mReceiver.getClass().getClassLoader();
                         intent.setExtrasClassLoader(cl);
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 86d0fd62..e2de716 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -37,7 +37,7 @@
  * While the LoaderManager API was introduced in
  * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
  * at is also available for use on older platforms through
- * {@link android.support.v4.app.FragmentActivity}.  See the blog post
+ * {@link androidx.fragment.app.FragmentActivity}.  See the blog post
  * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
  * Fragments For All</a> for more details.
  *
@@ -56,7 +56,7 @@
  * </div>
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.app.LoaderManager}
+ *      {@link androidx.loader.app.LoaderManager}
  */
 @Deprecated
 public abstract class LoaderManager {
@@ -64,7 +64,7 @@
      * Callback interface for a client to interact with the manager.
      *
      * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
-     *      Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
+     *      Support Library</a> {@link androidx.loader.app.LoaderManager.LoaderCallbacks}
      */
     @Deprecated
     public interface LoaderCallbacks<D> {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6a147720..9dd206e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1330,6 +1330,32 @@
     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
 
     /**
+     * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session
+     * associated with a {@link Notification.MediaStyle} notification. This will show in the media
+     * controls output switcher instead of the local device name.
+     * @hide
+     */
+    @TestApi
+    public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
+
+    /**
+     * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output
+     * switcher of the media controls for a {@link Notification.MediaStyle} notification.
+     * @hide
+     */
+    @TestApi
+    public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
+
+    /**
+     * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the
+     * media controls output switcher chip, associated with a {@link Notification.MediaStyle}
+     * notification. This should launch an activity.
+     * @hide
+     */
+    @TestApi
+    public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
+
+    /**
      * {@link #extras} key: the indices of actions to be shown in the compact view,
      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
      */
@@ -3719,7 +3745,7 @@
      * Provides a convenient way to set the various fields of a {@link Notification} and generate
      * content views using the platform's notification layout template. If your app supports
      * versions of Android as old as API level 4, you can instead use
-     * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
+     * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder},
      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
      * library</a>.
      *
@@ -8943,6 +8969,9 @@
 
         private int[] mActionsToShowInCompact = null;
         private MediaSession.Token mToken;
+        private CharSequence mDeviceName;
+        private int mDeviceIcon;
+        private PendingIntent mDeviceIntent;
 
         public MediaStyle() {
         }
@@ -8976,6 +9005,32 @@
         }
 
         /**
+         * For media notifications associated with playback on a remote device, provide device
+         * information that will replace the default values for the output switcher chip on the
+         * media control, as well as an intent to use when the output switcher chip is tapped,
+         * on devices where this is supported.
+         *
+         * @param deviceName The name of the remote device to display
+         * @param iconResource Icon resource representing the device
+         * @param chipIntent PendingIntent to send when the output switcher is tapped. May be
+         *                   {@code null}, in which case the output switcher will be disabled.
+         *                   This intent should open an Activity or it will be ignored.
+         * @return MediaStyle
+         *
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+        @NonNull
+        public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
+                @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) {
+            mDeviceName = deviceName;
+            mDeviceIcon = iconResource;
+            mDeviceIntent = chipIntent;
+            return this;
+        }
+
+        /**
          * @hide
          */
         @Override
@@ -9023,6 +9078,15 @@
             if (mActionsToShowInCompact != null) {
                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
             }
+            if (mDeviceName != null) {
+                extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName);
+            }
+            if (mDeviceIcon > 0) {
+                extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon);
+            }
+            if (mDeviceIntent != null) {
+                extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent);
+            }
         }
 
         /**
@@ -9038,6 +9102,15 @@
             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
             }
+            if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) {
+                mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE);
+            }
+            if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) {
+                mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON);
+            }
+            if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) {
+                mDeviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT);
+            }
         }
 
         /**
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index b40fbee..eadb7e3 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -610,6 +610,7 @@
         // Data can be too large for a transact. Write the data as a Blob, which will be written to
         // ashmem if too large.
         dest.writeBlob(data.marshall());
+        data.recycle();
     }
 
     public static final @NonNull Creator<NotificationHistory> CREATOR
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index da1ba52..5f00342 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -920,7 +920,7 @@
     public void setNavBarModeOverride(@NavBarModeOverride int navBarModeOverride) {
         if (navBarModeOverride != NAV_BAR_MODE_OVERRIDE_NONE
                 && navBarModeOverride != NAV_BAR_MODE_OVERRIDE_KIDS) {
-            throw new UnsupportedOperationException(
+            throw new IllegalArgumentException(
                     "Supplied navBarModeOverride not supported: " + navBarModeOverride);
         }
 
@@ -1012,6 +1012,8 @@
      *
      * @param displayState the new state for media tap-to-transfer.
      * @param routeInfo the media route information for the media being transferred.
+     * @param appIcon the icon of the app playing the media.
+     * @param appName the name of the app playing the media.
      *
      * @hide
      */
@@ -1019,11 +1021,13 @@
     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void updateMediaTapToTransferReceiverDisplay(
             @MediaTransferReceiverState int displayState,
-            @NonNull MediaRoute2Info routeInfo) {
+            @NonNull MediaRoute2Info routeInfo,
+            @Nullable Icon appIcon,
+            @Nullable CharSequence appName) {
         Objects.requireNonNull(routeInfo);
         IStatusBarService svc = getService();
         try {
-            svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo);
+            svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo, appIcon, appName);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 2af8905..5e521b0 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -733,7 +733,8 @@
         }
         // Calling out without a lock held.
         return AccessibilityInteractionClient.getInstance()
-                .getRootInActiveWindow(connectionId);
+                .getRootInActiveWindow(connectionId,
+                        AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
     }
 
     /**
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 73a9e5a..cfaffb1 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -517,7 +517,7 @@
      * (and potentially an Activity lifecycle event) being applied to all running apps.
      * Developers interested in an app-local implementation of night mode should consider using
      * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or
-     * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the
+     * {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)} for the
      * backward compatible implementation.
      *
      * @param mode the night mode to set
@@ -595,7 +595,7 @@
      * user clears the data for the application, or this application is uninstalled.
      * <p>
      * Developers interested in a non-persistent app-local implementation of night mode should
-     * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)}
+     * consider using {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)}
      * to manage the -night qualifier locally.
      *
      * @param mode the night mode to set
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e52ae51..6614be7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3703,8 +3703,9 @@
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.INTERACT_ACROSS_USERS})
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @UserHandleAware
+    @TestApi
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void acknowledgeNewUserDisclaimer() {
         if (mService != null) {
             try {
@@ -9713,9 +9714,9 @@
      * service. When zero or more packages have been added, accessibility services that are not in
      * the list and not part of the system can not be enabled by the user.
      * <p>
-     * Calling with a null value for the list disables the restriction so that all services can be
-     * used, calling with an empty list only allows the built-in system services. Any non-system
-     * accessibility service that's currently enabled must be included in the list.
+     * Calling with a {@code null} value for the list disables the restriction so that all services
+     * can be used, calling with an empty list only allows the built-in system services. Any
+     * non-system accessibility service that's currently enabled must be included in the list.
      * <p>
      * System accessibility services are always available to the user and this method can't
      * disable them.
@@ -9741,8 +9742,8 @@
     /**
      * Returns the list of permitted accessibility services set by this device or profile owner.
      * <p>
-     * An empty list means no accessibility services except system services are allowed. Null means
-     * all accessibility services are allowed.
+     * An empty list means no accessibility services except system services are allowed.
+     * {@code null} means all accessibility services are allowed.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @return List of accessiblity service package names.
@@ -9787,7 +9788,7 @@
      * Returns the list of accessibility services permitted by the device or profiles
      * owners of this user.
      *
-     * <p>Null means all accessibility services are allowed, if a non-null list is returned
+     * <p>{@code null} means all accessibility services are allowed, if a non-null list is returned
      * it will contain the intersection of the permitted lists for any device or profile
      * owners that apply to this user. It will also include any system accessibility services.
      *
@@ -9933,6 +9934,8 @@
      *
      * @return List of input method package names.
      * @hide
+     *
+     * @see #setPermittedAccessibilityServices(ComponentName, List)
      */
     @SystemApi
     @RequiresPermission(anyOf = {
@@ -9953,29 +9956,30 @@
     /**
      * Returns the list of input methods permitted.
      *
-     * <p>When this method returns empty list means all input methods are allowed, if a non-empty
-     * list is returned it will contain the intersection of the permitted lists for any device or
-     * profile owners that apply to this user. It will also include any system input methods.
+     * <p>{@code null} means all input methods are allowed, if a non-null list is returned
+     * it will contain the intersection of the permitted lists for any device or profile
+     * owners that apply to this user. It will also include any system input methods.
      *
      * @return List of input method package names.
      * @hide
+     *
+     * @see #setPermittedAccessibilityServices(ComponentName, List)
      */
     @UserHandleAware
     @RequiresPermission(allOf = {
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
             android.Manifest.permission.MANAGE_USERS
             }, conditional = true)
-    public @NonNull List<String> getPermittedInputMethods() {
+    public @Nullable List<String> getPermittedInputMethods() {
         throwIfParentInstance("getPermittedInputMethods");
-        List<String> result = null;
         if (mService != null) {
             try {
-                result = mService.getPermittedInputMethodsAsUser(myUserId());
+                return mService.getPermittedInputMethodsAsUser(myUserId());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
         }
-        return result != null ? result : Collections.emptyList();
+        return null;
     }
 
     /**
@@ -10330,6 +10334,7 @@
             throw re.rethrowFromSystemServer();
         }
     }
+
     /**
      * Gets the user a {@link #logoutUser(ComponentName)} call would switch to,
      * or {@code null} if the current user is not in a session (i.e., if it was not
diff --git a/core/java/android/app/cloudsearch/SearchRequest.java b/core/java/android/app/cloudsearch/SearchRequest.java
index 0c5c30c..ef66c0c 100644
--- a/core/java/android/app/cloudsearch/SearchRequest.java
+++ b/core/java/android/app/cloudsearch/SearchRequest.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.StringDef;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -91,6 +92,14 @@
     @NonNull
     private Bundle mSearchConstraints;
 
+    /** Auto set by system servier, and the caller cannot set it.
+     *
+     * The caller's package name.
+     *
+     */
+    @NonNull
+    private String mSource;
+
     private SearchRequest(Parcel in) {
         this.mQuery = in.readString();
         this.mResultOffset = in.readInt();
@@ -98,15 +107,17 @@
         this.mMaxLatencyMillis = in.readFloat();
         this.mSearchConstraints = in.readBundle();
         this.mId = in.readString();
+        this.mSource = in.readString();
     }
 
     private SearchRequest(String query, int resultOffset, int resultNumber, float maxLatencyMillis,
-            Bundle searchConstraints) {
+            Bundle searchConstraints, String source) {
         mQuery = query;
         mResultOffset = resultOffset;
         mResultNumber = resultNumber;
         mMaxLatencyMillis = maxLatencyMillis;
         mSearchConstraints = searchConstraints;
+        mSource = source;
     }
 
     /** Returns the original query. */
@@ -136,35 +147,37 @@
         return mSearchConstraints;
     }
 
+    /** Gets the caller's package name. */
+    @NonNull
+    public String getSource() {
+        return mSource;
+    }
+
     /** Returns the search request id, which is used to identify the request. */
     @NonNull
     public String getRequestId() {
         if (mId == null || mId.length() == 0) {
-            boolean isPresubmit =
-                    mSearchConstraints.containsKey(CONSTRAINT_IS_PRESUBMIT_SUGGESTION)
-                    && mSearchConstraints.getBoolean(CONSTRAINT_IS_PRESUBMIT_SUGGESTION);
-
-            String searchProvider = "EMPTY";
-            if (mSearchConstraints.containsKey(CONSTRAINT_SEARCH_PROVIDER_FILTER)) {
-                searchProvider = mSearchConstraints.getString(CONSTRAINT_SEARCH_PROVIDER_FILTER);
-            }
-
-            String rawContent = String.format("%s\t%d\t%d\t%f\t%b\t%s",
-                    mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
-                    isPresubmit, searchProvider);
-
-            mId = String.valueOf(rawContent.hashCode());
+            mId = String.valueOf(toString().hashCode());
         }
 
         return mId;
     }
 
+    /** Sets the caller, and this will be set by the system server.
+     *
+     * @hide
+     */
+    public void setSource(@NonNull String source) {
+        this.mSource = source;
+    }
+
     private SearchRequest(Builder b) {
         mQuery = requireNonNull(b.mQuery);
         mResultOffset = b.mResultOffset;
         mResultNumber = b.mResultNumber;
         mMaxLatencyMillis = b.mMaxLatencyMillis;
         mSearchConstraints = requireNonNull(b.mSearchConstraints);
+        mSource = requireNonNull(b.mSource);
     }
 
     /**
@@ -192,6 +205,7 @@
         dest.writeFloat(this.mMaxLatencyMillis);
         dest.writeBundle(this.mSearchConstraints);
         dest.writeString(getRequestId());
+        dest.writeString(this.mSource);
     }
 
     @Override
@@ -214,13 +228,30 @@
                 && mResultOffset == that.mResultOffset
                 && mResultNumber == that.mResultNumber
                 && mMaxLatencyMillis == that.mMaxLatencyMillis
-                && Objects.equals(mSearchConstraints, that.mSearchConstraints);
+                && Objects.equals(mSearchConstraints, that.mSearchConstraints)
+                && Objects.equals(mSource, that.mSource);
+    }
+
+    @Override
+    public String toString() {
+        boolean isPresubmit =
+                mSearchConstraints.containsKey(CONSTRAINT_IS_PRESUBMIT_SUGGESTION)
+                        && mSearchConstraints.getBoolean(CONSTRAINT_IS_PRESUBMIT_SUGGESTION);
+
+        String searchProvider = "EMPTY";
+        if (mSearchConstraints.containsKey(CONSTRAINT_SEARCH_PROVIDER_FILTER)) {
+            searchProvider = mSearchConstraints.getString(CONSTRAINT_SEARCH_PROVIDER_FILTER);
+        }
+
+        return String.format("SearchRequest: {query:%s,offset:%d;number:%d;max_latency:%f;"
+                        + "is_presubmit:%b;search_provider:%s;source:%s}", mQuery, mResultOffset,
+                mResultNumber, mMaxLatencyMillis, isPresubmit, searchProvider, mSource);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
-                mSearchConstraints);
+                mSearchConstraints, mSource);
     }
 
     /**
@@ -235,6 +266,7 @@
         private int mResultNumber;
         private float mMaxLatencyMillis;
         private Bundle mSearchConstraints;
+        private String mSource;
 
         /**
          *
@@ -250,6 +282,7 @@
             mResultNumber = 10;
             mMaxLatencyMillis = 200;
             mSearchConstraints = Bundle.EMPTY;
+            mSource = "DEFAULT_CALLER";
         }
 
         /** Sets the input query. */
@@ -288,6 +321,17 @@
             return this;
         }
 
+        /** Sets the caller, and this will be set by the system server.
+         *
+         * @hide
+         */
+        @NonNull
+        @TestApi
+        public Builder setSource(@NonNull String source) {
+            this.mSource = source;
+            return this;
+        }
+
         /** Builds a SearchRequest based-on the given params. */
         @NonNull
         public SearchRequest build() {
@@ -297,7 +341,7 @@
             }
 
             return new SearchRequest(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
-                               mSearchConstraints);
+                               mSearchConstraints, mSource);
         }
     }
 }
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 373a8d9..f7f0235 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -207,6 +207,18 @@
         return macAddress.equals(mDeviceMacAddress);
     }
 
+    /**
+     * Utility method to be used by CdmService only.
+     *
+     * @return whether CdmService should bind the companion application that "owns" this association
+     *         when the device is present.
+     *
+     * @hide
+     */
+    public boolean shouldBindWhenPresent() {
+        return mNotifyOnDeviceNearby || mSelfManaged;
+    }
+
     /** @hide */
     public @NonNull String toShortString() {
         final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 36802eabe..1568500 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -780,9 +780,9 @@
     }
 
     /**
-     * Notify the system that the given self-managed association has just 'appeared'.
+     * Notify the system that the given self-managed association has just appeared.
      * This causes the system to bind to the companion app to keep it running until the association
-     * is reported as 'disappeared'
+     * is reported as disappeared
      *
      * <p>This API is only available for the companion apps that manage the connectivity by
      * themselves.</p>
@@ -803,7 +803,7 @@
     }
 
     /**
-     * Notify the system that the given self-managed association has just 'disappeared'.
+     * Notify the system that the given self-managed association has just disappeared.
      * This causes the system to unbind to the companion app.
      *
      * <p>This API is only available for the companion apps that manage the connectivity by
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index cb96ebe..9e1bf4b 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -77,10 +77,11 @@
  * {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)}
  * only to one "primary" services.
  * Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary"
- * service using "android.companion.primary" tag.
+ * service using "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE" service level
+ * property.
  * <pre>{@code
- * <meta-data
- *       android:name="android.companion.primary"
+ * <property
+ *       android:name="android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE"
  *       android:value="true" />
  * }</pre>
  *
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 8fc24fd..e2859998 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -17,6 +17,7 @@
 package android.companion.virtual;
 
 import android.app.PendingIntent;
+import android.companion.virtual.audio.IAudioSessionCallback;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.hardware.input.VirtualKeyEvent;
@@ -45,6 +46,15 @@
      */
     void close();
 
+    /**
+     * Notifies of an audio session being started.
+     */
+    void onAudioSessionStarting(
+            int displayId,
+            IAudioSessionCallback callback);
+
+    void onAudioSessionEnded();
+
     void createVirtualKeyboard(
             int displayId,
             String inputDeviceName,
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 1dbe04c..fdff27f 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -25,6 +25,8 @@
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.companion.AssociationInfo;
+import android.companion.virtual.audio.VirtualAudioDevice;
+import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Point;
@@ -263,8 +265,8 @@
          *
          * @param display the display that the events inputted through this device should target
          * @param inputDeviceName the name to call this input device
-         * @param vendorId the vendor id
-         * @param productId the product id
+         * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id)
+         * @param productId the product id, as defined by uinput's uinput_setup struct
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
@@ -289,8 +291,8 @@
          *
          * @param display the display that the events inputted through this device should target
          * @param inputDeviceName the name to call this input device
-         * @param vendorId the vendor id
-         * @param productId the product id
+         * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id)
+         * @param productId the product id, as defined by uinput's uinput_setup struct
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
@@ -315,8 +317,8 @@
          *
          * @param display the display that the events inputted through this device should target
          * @param inputDeviceName the name to call this input device
-         * @param vendorId the vendor id
-         * @param productId the product id
+         * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id)
+         * @param productId the product id, as defined by uinput's uinput_setup struct
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
@@ -339,6 +341,30 @@
         }
 
         /**
+         * Creates a VirtualAudioDevice, capable of recording audio emanating from this device,
+         * or injecting audio from another device.
+         *
+         * <p>Note: This object does not support capturing privileged playback, such as voice call
+         * audio.
+         *
+         * @param display The target virtual display to capture from and inject into.
+         * @param executor The {@link Executor} object for the thread on which to execute
+         *                the callback. If <code>null</code>, the {@link Executor} associated with
+         *                the main {@link Looper} will be used.
+         * @param callback Interface to be notified when playback or recording configuration of
+         *                applications running on virtual display is changed.
+         * @return A {@link VirtualAudioDevice} instance.
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualAudioDevice createVirtualAudioDevice(
+                @NonNull VirtualDisplay display,
+                @Nullable Executor executor,
+                @Nullable AudioConfigurationChangeCallback callback) {
+            return new VirtualAudioDevice(mContext, mVirtualDevice, display, executor, callback);
+        }
+
+        /**
          * Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
          *
          * @param showPointerIcon True if the pointer should be shown; false otherwise. The default
diff --git a/core/java/android/companion/virtual/audio/AudioCapture.java b/core/java/android/companion/virtual/audio/AudioCapture.java
new file mode 100644
index 0000000..ebe17db
--- /dev/null
+++ b/core/java/android/companion/virtual/audio/AudioCapture.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.companion.virtual.audio;
+
+import static android.media.AudioRecord.RECORDSTATE_RECORDING;
+import static android.media.AudioRecord.RECORDSTATE_STOPPED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.media.AudioRecord;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Wrapper around {@link AudioRecord} that allows for the underlying {@link AudioRecord} to
+ * be swapped out while recording is ongoing.
+ *
+ * @hide
+ */
+// The stop() actually doesn't release resources, so should not force implementing Closeable.
+@SuppressLint("NotCloseable")
+@SystemApi
+public final class AudioCapture {
+    private static final String TAG = "AudioCapture";
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    @Nullable
+    private AudioRecord mAudioRecord;
+
+    @GuardedBy("mLock")
+    private int mRecordingState = RECORDSTATE_STOPPED;
+
+    /**
+     * Sets the {@link AudioRecord} to handle audio capturing.
+     * Callers may call this multiple times with different audio records to change
+     * the underlying {@link AudioRecord} without stopping and re-starting recording.
+     *
+     * @param audioRecord The underlying {@link AudioRecord} to use for capture,
+     * or null if no audio (i.e. silence) should be captured while still keeping the
+     * record in a recording state.
+     */
+    void setAudioRecord(@Nullable AudioRecord audioRecord) {
+        Log.d(TAG, "set AudioRecord with " + audioRecord);
+        synchronized (mLock) {
+            // Release old reference.
+            if (mAudioRecord != null) {
+                mAudioRecord.release();
+            }
+            // Sync recording state for new reference.
+            if (audioRecord != null) {
+                if (mRecordingState == RECORDSTATE_RECORDING
+                        && audioRecord.getRecordingState() != RECORDSTATE_RECORDING) {
+                    audioRecord.startRecording();
+                }
+                if (mRecordingState == RECORDSTATE_STOPPED
+                        && audioRecord.getRecordingState() != RECORDSTATE_STOPPED) {
+                    audioRecord.stop();
+                }
+            }
+            mAudioRecord = audioRecord;
+        }
+    }
+
+    /** See {@link AudioRecord#read(ByteBuffer, int)}. */
+    public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) {
+        final int sizeRead;
+        synchronized (mLock) {
+            if (mAudioRecord != null) {
+                sizeRead = mAudioRecord.read(audioBuffer, sizeInBytes);
+            } else {
+                sizeRead = 0;
+            }
+        }
+        return sizeRead;
+    }
+
+    /** See {@link AudioRecord#startRecording()}. */
+    public void startRecording() {
+        synchronized (mLock) {
+            mRecordingState = RECORDSTATE_RECORDING;
+            if (mAudioRecord != null && mAudioRecord.getRecordingState() != RECORDSTATE_RECORDING) {
+                mAudioRecord.startRecording();
+            }
+        }
+    }
+
+    /** See {@link AudioRecord#stop()}. */
+    public void stop() {
+        synchronized (mLock) {
+            mRecordingState = RECORDSTATE_STOPPED;
+            if (mAudioRecord != null && mAudioRecord.getRecordingState() != RECORDSTATE_STOPPED) {
+                mAudioRecord.stop();
+            }
+        }
+    }
+
+    /** See {@link AudioRecord#getRecordingState()}. */
+    public int getRecordingState() {
+        synchronized (mLock) {
+            return mRecordingState;
+        }
+    }
+}
diff --git a/core/java/android/companion/virtual/audio/AudioInjection.java b/core/java/android/companion/virtual/audio/AudioInjection.java
new file mode 100644
index 0000000..5e8e0a4
--- /dev/null
+++ b/core/java/android/companion/virtual/audio/AudioInjection.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.audio;
+
+import static android.media.AudioTrack.PLAYSTATE_PLAYING;
+import static android.media.AudioTrack.PLAYSTATE_STOPPED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.media.AudioTrack;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Wrapper around {@link AudioTrack} that allows for the underlying {@link AudioTrack} to
+ * be swapped out while playout is ongoing.
+ *
+ * @hide
+ */
+// The stop() actually doesn't release resources, so should not force implementing Closeable.
+@SuppressLint("NotCloseable")
+@SystemApi
+public final class AudioInjection {
+    private static final String TAG = "AudioInjection";
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    @Nullable
+    private AudioTrack mAudioTrack;
+    @GuardedBy("mLock")
+    private int mPlayState = PLAYSTATE_STOPPED;
+    @GuardedBy("mLock")
+    private boolean mIsSilent;
+
+    /** Sets if the injected microphone sound is silent. */
+    void setSilent(boolean isSilent) {
+        synchronized (mLock) {
+            mIsSilent = isSilent;
+        }
+    }
+
+    /**
+     * Sets the {@link AudioTrack} to handle audio injection.
+     * Callers may call this multiple times with different audio tracks to change
+     * the underlying {@link AudioTrack} without stopping and re-starting injection.
+     *
+     * @param audioTrack The underlying {@link AudioTrack} to use for injection,
+     * or null if no audio (i.e. silence) should be injected while still keeping the
+     * record in a playing state.
+     */
+    void setAudioTrack(@Nullable AudioTrack audioTrack) {
+        Log.d(TAG, "set AudioTrack with " + audioTrack);
+        synchronized (mLock) {
+            // Release old reference.
+            if (mAudioTrack != null) {
+                mAudioTrack.release();
+            }
+            // Sync play state for new reference.
+            if (audioTrack != null) {
+                if (mPlayState == PLAYSTATE_PLAYING
+                        && audioTrack.getPlayState() != PLAYSTATE_PLAYING) {
+                    audioTrack.play();
+                }
+                if (mPlayState == PLAYSTATE_STOPPED
+                        && audioTrack.getPlayState() != PLAYSTATE_STOPPED) {
+                    audioTrack.stop();
+                }
+            }
+            mAudioTrack = audioTrack;
+        }
+    }
+
+    /** See {@link AudioTrack#write(ByteBuffer, int, int)}. */
+    public int write(@NonNull ByteBuffer audioBuffer, int sizeInBytes, int writeMode) {
+        final int sizeWrite;
+        synchronized (mLock) {
+            if (mAudioTrack != null && !mIsSilent) {
+                sizeWrite = mAudioTrack.write(audioBuffer, sizeInBytes, writeMode);
+            } else {
+                sizeWrite = 0;
+            }
+        }
+        return sizeWrite;
+    }
+
+    /** See {@link AudioTrack#play()}. */
+    public void play() {
+        synchronized (mLock) {
+            mPlayState = PLAYSTATE_PLAYING;
+            if (mAudioTrack != null && mAudioTrack.getPlayState() != PLAYSTATE_PLAYING) {
+                mAudioTrack.play();
+            }
+        }
+    }
+
+    /** See {@link AudioTrack#stop()}. */
+    public void stop() {
+        synchronized (mLock) {
+            mPlayState = PLAYSTATE_STOPPED;
+            if (mAudioTrack != null && mAudioTrack.getPlayState() != PLAYSTATE_STOPPED) {
+                mAudioTrack.stop();
+            }
+        }
+    }
+
+    /** See {@link AudioTrack#getPlayState()}. */
+    public int getPlayState() {
+        synchronized (mLock) {
+            return mPlayState;
+        }
+    }
+}
diff --git a/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl b/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl
new file mode 100644
index 0000000..e22ce14
--- /dev/null
+++ b/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.companion.virtual.audio;
+
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
+
+/**
+ * Callback to control audio rerouting, notify playback and recording state of applications running
+ * on virtual device.
+ *
+ * @hide
+ */
+oneway interface IAudioSessionCallback {
+
+    /** Updates the set of applications that need to have their audio rerouted. */
+    void onAppsNeedingAudioRoutingChanged(in int[] appUids);
+
+    /**
+     * Called whenever the playback configuration of applications running on virtual device has
+     * changed.
+     */
+    void onPlaybackConfigChanged(in List<AudioPlaybackConfiguration> configs);
+
+    /**
+     * Called whenever the recording configuration of applications running on virtual device has
+     * changed.
+     */
+    void onRecordingConfigChanged(in List<AudioRecordingConfiguration> configs);
+}
diff --git a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java
new file mode 100644
index 0000000..5c246d3
--- /dev/null
+++ b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.audio;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Class to detect the user restrictions change for microphone usage.
+ */
+final class UserRestrictionsDetector extends BroadcastReceiver {
+    private static final String TAG = "UserRestrictionsDetector";
+
+    /** Interface for listening user restrictions change. */
+    interface UserRestrictionsCallback {
+
+        /** Notifies when value of {@link UserManager#DISALLOW_UNMUTE_MICROPHONE} is changed. */
+        void onMicrophoneRestrictionChanged(boolean isUnmuteMicDisallowed);
+    }
+
+    private final Context mContext;
+    private final UserManager mUserManager;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private boolean mIsUnmuteMicDisallowed;
+    private UserRestrictionsCallback mUserRestrictionsCallback;
+
+    UserRestrictionsDetector(Context context) {
+        mContext = context;
+        mUserManager = context.getSystemService(UserManager.class);
+    }
+
+    /** Returns value of {@link UserManager#DISALLOW_UNMUTE_MICROPHONE}. */
+    boolean isUnmuteMicrophoneDisallowed() {
+        Bundle bundle = mUserManager.getUserRestrictions();
+        return bundle.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE);
+    }
+
+    /** Registers user restrictions change. */
+    void register(@NonNull UserRestrictionsCallback callback) {
+        mUserRestrictionsCallback = callback;
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+        mContext.registerReceiver(/* receiver= */ this, filter);
+
+        synchronized (mLock) {
+            // Gets initial value.
+            mIsUnmuteMicDisallowed = isUnmuteMicrophoneDisallowed();
+        }
+    }
+
+    /** Unregisters user restrictions change. */
+    void unregister() {
+        mUserRestrictionsCallback = null;
+        mContext.unregisterReceiver(/* receiver= */ this);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (UserManager.ACTION_USER_RESTRICTIONS_CHANGED.equals(action)) {
+            boolean isUnmuteMicDisallowed = isUnmuteMicrophoneDisallowed();
+            synchronized (mLock) {
+                if (isUnmuteMicDisallowed == mIsUnmuteMicDisallowed) {
+                    return;
+                }
+                mIsUnmuteMicDisallowed = isUnmuteMicDisallowed;
+            }
+            if (mUserRestrictionsCallback != null) {
+                mUserRestrictionsCallback.onMicrophoneRestrictionChanged(isUnmuteMicDisallowed);
+            }
+        }
+    }
+}
diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
new file mode 100644
index 0000000..38e37ec
--- /dev/null
+++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.companion.virtual.audio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.content.Context;
+import android.hardware.display.VirtualDisplay;
+import android.media.AudioFormat;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
+import android.os.RemoteException;
+
+import java.io.Closeable;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * The class stores an {@link AudioCapture} for audio capturing and an {@link AudioInjection} for
+ * audio injection.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualAudioDevice implements Closeable {
+
+    /**
+     * Interface to be notified when playback or recording configuration of applications running on
+     * virtual display was changed.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface AudioConfigurationChangeCallback {
+        /**
+         * Notifies when playback configuration of applications running on virtual display was
+         * changed.
+         */
+        void onPlaybackConfigChanged(@NonNull List<AudioPlaybackConfiguration> configs);
+
+        /**
+         * Notifies when recording configuration of applications running on virtual display was
+         * changed.
+         */
+        void onRecordingConfigChanged(@NonNull List<AudioRecordingConfiguration> configs);
+    }
+
+    private final Context mContext;
+    private final IVirtualDevice mVirtualDevice;
+    private final VirtualDisplay mVirtualDisplay;
+    private final AudioConfigurationChangeCallback mCallback;
+    private final Executor mExecutor;
+    @Nullable
+    private VirtualAudioSession mOngoingSession;
+
+    /**
+     * @hide
+     */
+    public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice,
+            VirtualDisplay virtualDisplay, Executor executor,
+            AudioConfigurationChangeCallback callback) {
+        mContext = context;
+        mVirtualDevice = virtualDevice;
+        mVirtualDisplay = virtualDisplay;
+        mExecutor = executor;
+        mCallback = callback;
+    }
+
+    /**
+     * Begins injecting audio from a remote device into this device.
+     *
+     * @return An {@link AudioInjection} containing the injected audio.
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @NonNull
+    public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) {
+        Objects.requireNonNull(injectionFormat, "injectionFormat must not be null");
+
+        if (mOngoingSession != null && mOngoingSession.getAudioInjection() != null) {
+            throw new IllegalStateException("Cannot start an audio injection while a session is "
+                    + "ongoing. Call close() on this device first to end the previous injection.");
+        }
+        if (mOngoingSession == null) {
+            mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor);
+        }
+
+        try {
+            mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(),
+                    /* callback= */ mOngoingSession);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return mOngoingSession.startAudioInjection(injectionFormat);
+    }
+
+    /**
+     * Begins recording audio emanating from this device.
+     *
+     * @return An {@link AudioCapture} containing the recorded audio.
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @NonNull
+    public AudioCapture startAudioCapture(@NonNull AudioFormat captureFormat) {
+        Objects.requireNonNull(captureFormat, "captureFormat must not be null");
+
+        if (mOngoingSession != null && mOngoingSession.getAudioCapture() != null) {
+            throw new IllegalStateException("Cannot start an audio capture while a session is "
+                    + "ongoing. Call close() on this device first to end the previous session.");
+        }
+        if (mOngoingSession == null) {
+            mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor);
+        }
+
+        try {
+            mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(),
+                    /* callback= */ mOngoingSession);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return mOngoingSession.startAudioCapture(captureFormat);
+    }
+
+    /** Returns the {@link AudioCapture} instance. */
+    @Nullable
+    public AudioCapture getAudioCapture() {
+        return mOngoingSession != null ? mOngoingSession.getAudioCapture() : null;
+    }
+
+    /** Returns the {@link AudioInjection} instance. */
+    @Nullable
+    public AudioInjection getAudioInjection() {
+        return mOngoingSession != null ? mOngoingSession.getAudioInjection() : null;
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Override
+    public void close() {
+        if (mOngoingSession != null) {
+            mOngoingSession.close();
+            mOngoingSession = null;
+
+            try {
+                mVirtualDevice.onAudioSessionEnded();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/core/java/android/companion/virtual/audio/VirtualAudioSession.java b/core/java/android/companion/virtual/audio/VirtualAudioSession.java
new file mode 100644
index 0000000..bc71bd6
--- /dev/null
+++ b/core/java/android/companion/virtual/audio/VirtualAudioSession.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.companion.virtual.audio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.companion.virtual.audio.UserRestrictionsDetector.UserRestrictionsCallback;
+import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecord;
+import android.media.AudioRecordingConfiguration;
+import android.media.AudioTrack;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
+import android.util.IntArray;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.Closeable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Manages an ongiong audio session in which audio can be captured (recorded) and/or
+ * injected from a remote device.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public final class VirtualAudioSession extends IAudioSessionCallback.Stub implements
+        UserRestrictionsCallback, Closeable {
+    private static final String TAG = "VirtualAudioSession";
+
+    private final Context mContext;
+    private final UserRestrictionsDetector mUserRestrictionsDetector;
+    /** The {@link Executor} for sending {@link AudioConfigurationChangeCallback} to the caller */
+    private final Executor mExecutor;
+    @Nullable
+    private final AudioConfigurationChangeCallback mCallback;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final IntArray mReroutedAppUids = new IntArray();
+    @Nullable
+    @GuardedBy("mLock")
+    private AudioPolicy mAudioPolicy;
+    @Nullable
+    @GuardedBy("mLock")
+    private AudioFormat mCaptureFormat;
+    @Nullable
+    @GuardedBy("mLock")
+    private AudioFormat mInjectionFormat;
+    @Nullable
+    @GuardedBy("mLock")
+    private AudioCapture mAudioCapture;
+    @Nullable
+    @GuardedBy("mLock")
+    private AudioInjection mAudioInjection;
+
+    @VisibleForTesting
+    public VirtualAudioSession(Context context,
+            @Nullable AudioConfigurationChangeCallback callback, @Nullable Executor executor) {
+        mContext = context;
+        mUserRestrictionsDetector = new UserRestrictionsDetector(context);
+        mCallback = callback;
+        mExecutor = executor != null ? executor : context.getMainExecutor();
+    }
+
+    /**
+     * Begins recording audio emanating from this device.
+     *
+     * @return An {@link AudioCapture} containing the recorded audio.
+     */
+    @VisibleForTesting
+    @NonNull
+    public AudioCapture startAudioCapture(@NonNull AudioFormat captureFormat) {
+        Objects.requireNonNull(captureFormat, "captureFormat must not be null");
+
+        synchronized (mLock) {
+            if (mAudioCapture != null) {
+                throw new IllegalStateException(
+                        "Cannot start capture while another capture is ongoing.");
+            }
+
+            mCaptureFormat = captureFormat;
+            mAudioCapture = new AudioCapture();
+            mAudioCapture.startRecording();
+            return mAudioCapture;
+        }
+    }
+
+    /**
+     * Begins injecting audio from a remote device into this device.
+     *
+     * @return An {@link AudioInjection} containing the injected audio.
+     */
+    @VisibleForTesting
+    @NonNull
+    public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) {
+        Objects.requireNonNull(injectionFormat, "injectionFormat must not be null");
+        mUserRestrictionsDetector.register(/* callback= */ this);
+        synchronized (mLock) {
+            if (mAudioInjection != null) {
+                throw new IllegalStateException(
+                        "Cannot start injection while injection is already ongoing.");
+            }
+
+            mInjectionFormat = injectionFormat;
+            mAudioInjection = new AudioInjection();
+            mAudioInjection.play();
+            mAudioInjection.setSilent(mUserRestrictionsDetector.isUnmuteMicrophoneDisallowed());
+            return mAudioInjection;
+        }
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    @Nullable
+    public AudioCapture getAudioCapture() {
+        synchronized (mLock) {
+            return mAudioCapture;
+        }
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    @Nullable
+    public AudioInjection getAudioInjection() {
+        synchronized (mLock) {
+            return mAudioInjection;
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Override
+    public void onAppsNeedingAudioRoutingChanged(int[] appUids) {
+        synchronized (mLock) {
+            if (Arrays.equals(mReroutedAppUids.toArray(), appUids)) {
+                return;
+            }
+        }
+
+        releaseAudioStreams();
+
+        if (appUids.length == 0) {
+            return;
+        }
+
+        createAudioStreams(appUids);
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Override
+    public void close() {
+        mUserRestrictionsDetector.unregister();
+        releaseAudioStreams();
+        synchronized (mLock) {
+            mAudioCapture = null;
+            mAudioInjection = null;
+            mCaptureFormat = null;
+            mInjectionFormat = null;
+        }
+    }
+
+    @Override
+    public void onMicrophoneRestrictionChanged(boolean isUnmuteMicDisallowed) {
+        synchronized (mLock) {
+            if (mAudioInjection != null) {
+                mAudioInjection.setSilent(isUnmuteMicDisallowed);
+            }
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    private void createAudioStreams(int[] appUids) {
+        synchronized (mLock) {
+            if (mCaptureFormat == null && mInjectionFormat == null) {
+                throw new IllegalStateException(
+                        "At least one of captureFormat and injectionFormat must be specified.");
+            }
+            if (mAudioPolicy != null) {
+                throw new IllegalStateException(
+                        "Cannot create audio streams while the audio policy is registered. Call "
+                                + "releaseAudioStreams() first to unregister the previous audio "
+                                + "policy."
+                );
+            }
+
+            mReroutedAppUids.clear();
+            for (int appUid : appUids) {
+                mReroutedAppUids.add(appUid);
+            }
+
+            AudioMix audioRecordMix = null;
+            AudioMix audioTrackMix = null;
+            AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
+            if (mCaptureFormat != null) {
+                audioRecordMix = createAudioRecordMix(mCaptureFormat, appUids);
+                builder.addMix(audioRecordMix);
+            }
+            if (mInjectionFormat != null) {
+                audioTrackMix = createAudioTrackMix(mInjectionFormat, appUids);
+                builder.addMix(audioTrackMix);
+            }
+            mAudioPolicy = builder.build();
+            AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+            if (audioManager.registerAudioPolicy(mAudioPolicy) == AudioManager.ERROR) {
+                Log.e(TAG, "Failed to register audio policy!");
+            }
+
+            AudioRecord audioRecord =
+                    audioRecordMix != null ? mAudioPolicy.createAudioRecordSink(audioRecordMix)
+                            : null;
+            AudioTrack audioTrack =
+                    audioTrackMix != null ? mAudioPolicy.createAudioTrackSource(audioTrackMix)
+                            : null;
+
+            if (mAudioCapture != null) {
+                mAudioCapture.setAudioRecord(audioRecord);
+            }
+            if (mAudioInjection != null) {
+                mAudioInjection.setAudioTrack(audioTrack);
+            }
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    private void releaseAudioStreams() {
+        synchronized (mLock) {
+            if (mAudioCapture != null) {
+                mAudioCapture.setAudioRecord(null);
+            }
+            if (mAudioInjection != null) {
+                mAudioInjection.setAudioTrack(null);
+            }
+            mReroutedAppUids.clear();
+            if (mAudioPolicy != null) {
+                AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+                audioManager.unregisterAudioPolicy(mAudioPolicy);
+                mAudioPolicy = null;
+                Log.i(TAG, "AudioPolicy unregistered");
+            }
+        }
+    }
+
+    @Override
+    public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+        if (mCallback != null) {
+            mExecutor.execute(() -> mCallback.onPlaybackConfigChanged(configs));
+        }
+    }
+
+    @Override
+    public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+        if (mCallback != null) {
+            mExecutor.execute(() -> mCallback.onRecordingConfigChanged(configs));
+        }
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public IntArray getReroutedAppUids() {
+        synchronized (mLock) {
+            return mReroutedAppUids;
+        }
+    }
+
+    private static AudioMix createAudioRecordMix(@NonNull AudioFormat audioFormat, int[] appUids) {
+        AudioMixingRule.Builder builder = new AudioMixingRule.Builder();
+        builder.setTargetMixRole(AudioMixingRule.MIX_ROLE_PLAYERS);
+        for (int uid : appUids) {
+            builder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
+        }
+        AudioMixingRule audioMixingRule = builder.allowPrivilegedPlaybackCapture(false).build();
+        AudioMix audioMix =
+                new AudioMix.Builder(audioMixingRule)
+                        .setFormat(audioFormat)
+                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+                        .build();
+        return audioMix;
+    }
+
+    private static AudioMix createAudioTrackMix(@NonNull AudioFormat audioFormat, int[] appUids) {
+        AudioMixingRule.Builder builder = new AudioMixingRule.Builder();
+        builder.setTargetMixRole(AudioMixingRule.MIX_ROLE_INJECTOR);
+        for (int uid : appUids) {
+            builder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
+        }
+        AudioMixingRule audioMixingRule = builder.build();
+        AudioMix audioMix =
+                new AudioMix.Builder(audioMixingRule)
+                        .setFormat(audioFormat)
+                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+                        .build();
+        return audioMix;
+    }
+}
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 14c3387..3e544b2 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -52,7 +52,7 @@
  * @param <D> the data type to be loaded.
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.content.AsyncTaskLoader}
+ *      {@link androidx.loader.content.AsyncTaskLoader}
  */
 @Deprecated
 public abstract class AsyncTaskLoader<D> extends Loader<D> {
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index d46a0c6..2a19d37 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -376,7 +376,7 @@
      * to run, allowing them to execute for 30 seconds or even a bit more.  This is something that
      * receivers should rarely take advantage of (long work should be punted to another system
      * facility such as {@link android.app.job.JobScheduler}, {@link android.app.Service}, or
-     * see especially {@link android.support.v4.app.JobIntentService}), but can be useful in
+     * see especially {@link androidx.core.app.JobIntentService}), but can be useful in
      * certain rare cases where it is necessary to do some work as soon as the broadcast is
      * delivered.  Keep in mind that the work you do here will block further broadcasts until
      * it completes, so taking advantage of this at all excessively can be counter-productive
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2074125..957cb24c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -36,6 +36,7 @@
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.annotation.UserIdInt;
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.BroadcastOptions;
 import android.app.GameManager;
@@ -45,6 +46,8 @@
 import android.app.ambientcontext.AmbientContextManager;
 import android.app.people.PeopleManager;
 import android.app.time.TimeManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -85,6 +88,7 @@
 import android.view.textclassifier.TextClassificationManager;
 import android.window.WindowContext;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.compat.IPlatformCompatNative;
 
@@ -111,6 +115,19 @@
  * broadcasting and receiving intents, etc.
  */
 public abstract class Context {
+    /**
+     * After {@link Build.VERSION_CODES#TIRAMISU},
+     * {@link #registerComponentCallbacks(ComponentCallbacks)} will add a {@link ComponentCallbacks}
+     * to {@link Activity} or {@link ContextWrapper#getBaseContext()} instead of always adding to
+     * {@link #getApplicationContext()}.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @VisibleForTesting
+    public static final long OVERRIDABLE_COMPONENT_CALLBACKS = 193247900L;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "MODE_" }, value = {
             MODE_PRIVATE,
@@ -163,7 +180,7 @@
      *             {@link BroadcastReceiver}, and {@link android.app.Service}.
      *             There are no guarantees that this access mode will remain on
      *             a file, such as when it goes through a backup and restore.
-     * @see android.support.v4.content.FileProvider
+     * @see androidx.core.content.FileProvider
      * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
      */
     @Deprecated
@@ -183,7 +200,7 @@
      *             {@link BroadcastReceiver}, and {@link android.app.Service}.
      *             There are no guarantees that this access mode will remain on
      *             a file, such as when it goes through a backup and restore.
-     * @see android.support.v4.content.FileProvider
+     * @see androidx.core.content.FileProvider
      * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
      */
     @Deprecated
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 98ced6d..9adf173 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -25,8 +25,6 @@
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -74,16 +72,6 @@
     Context mBase;
 
     /**
-     * After {@link Build.VERSION_CODES#TIRAMISU},
-     * {@link #registerComponentCallbacks(ComponentCallbacks)} will delegate to
-     * {@link #getBaseContext()} instead of {@link #getApplicationContext()}.
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
-    @VisibleForTesting
-    static final long COMPONENT_CALLBACK_ON_WRAPPER = 193247900L;
-
-    /**
      * A list to store {@link ComponentCallbacks} which
      * passes to {@link #registerComponentCallbacks(ComponentCallbacks)} before
      * {@link #attachBaseContext(Context)}.
@@ -1355,7 +1343,7 @@
     public void registerComponentCallbacks(ComponentCallbacks callback) {
         if (mBase != null) {
             mBase.registerComponentCallbacks(callback);
-        } else if (!CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+        } else if (!CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) {
             super.registerComponentCallbacks(callback);
             synchronized (mLock) {
                 // Also register ComponentCallbacks to ContextWrapper, so we can find the correct
@@ -1397,7 +1385,7 @@
                 mCallbacksRegisteredToSuper.remove(callback);
             } else if (mBase != null) {
                 mBase.unregisterComponentCallbacks(callback);
-            } else if (CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+            } else if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) {
                 // Throw exception for Application that is targeting S-v2+
                 throw new IllegalStateException("ComponentCallbacks must be unregistered after "
                         + "this ContextWrapper is attached to a base Context.");
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index fda646c..cfb0f95 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -42,7 +42,7 @@
  * and {@link #setProjection(String[])}.
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.content.CursorLoader}
+ *      {@link androidx.loader.content.CursorLoader}
  */
 @Deprecated
 public class CursorLoader extends AsyncTaskLoader<Cursor> {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index b0555d4..afd495b 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -50,7 +50,7 @@
  * @param <D> The result returned when the load is complete
  *
  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
- *      {@link android.support.v4.content.Loader}
+ *      {@link androidx.loader.content.Loader}
  */
 @Deprecated
 public class Loader<D> {
@@ -71,7 +71,7 @@
      * it is used for you by {@link CursorLoader} to take care of executing
      * an update when the cursor's backing data changes.
      *
-     * @deprecated Use {@link android.support.v4.content.Loader.ForceLoadContentObserver}
+     * @deprecated Use {@link androidx.loader.content.Loader.ForceLoadContentObserver}
      */
     @Deprecated
     public final class ForceLoadContentObserver extends ContentObserver {
@@ -98,7 +98,7 @@
      * be reported to its client.  This interface should only be used if a
      * Loader is not being used in conjunction with LoaderManager.
      *
-     * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCompleteListener}
+     * @deprecated Use {@link androidx.loader.content.Loader.OnLoadCompleteListener}
      */
     @Deprecated
     public interface OnLoadCompleteListener<D> {
@@ -119,7 +119,7 @@
      * can schedule the next Loader.  This interface should only be used if a
      * Loader is not being used in conjunction with LoaderManager.
      *
-     * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCanceledListener}
+     * @deprecated Use {@link androidx.loader.content.Loader.OnLoadCanceledListener}
      */
     @Deprecated
     public interface OnLoadCanceledListener<D> {
diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java
index 4f38fae..9874890 100644
--- a/core/java/android/content/integrity/AppInstallMetadata.java
+++ b/core/java/android/content/integrity/AppInstallMetadata.java
@@ -37,6 +37,8 @@
     private final String mPackageName;
     // Raw string encoding for the SHA-256 hash of the certificate of the app.
     private final List<String> mAppCertificates;
+    // Raw string encoding for the SHA-256 hash of the certificate lineage/history of the app.
+    private final List<String> mAppCertificateLineage;
     private final String mInstallerName;
     // Raw string encoding for the SHA-256 hash of the certificate of the installer.
     private final List<String> mInstallerCertificates;
@@ -52,6 +54,7 @@
     private AppInstallMetadata(Builder builder) {
         this.mPackageName = builder.mPackageName;
         this.mAppCertificates = builder.mAppCertificates;
+        this.mAppCertificateLineage = builder.mAppCertificateLineage;
         this.mInstallerName = builder.mInstallerName;
         this.mInstallerCertificates = builder.mInstallerCertificates;
         this.mVersionCode = builder.mVersionCode;
@@ -74,6 +77,11 @@
     }
 
     @NonNull
+    public List<String> getAppCertificateLineage() {
+        return mAppCertificateLineage;
+    }
+
+    @NonNull
     public String getInstallerName() {
         return mInstallerName;
     }
@@ -126,6 +134,7 @@
                     + " %b, StampVerified = %b, StampTrusted = %b, StampCert = %s }",
                 mPackageName,
                 mAppCertificates,
+                mAppCertificateLineage,
                 mInstallerName == null ? "null" : mInstallerName,
                 mInstallerCertificates == null ? "null" : mInstallerCertificates,
                 mVersionCode,
@@ -140,6 +149,7 @@
     public static final class Builder {
         private String mPackageName;
         private List<String> mAppCertificates;
+        private List<String> mAppCertificateLineage;
         private String mInstallerName;
         private List<String> mInstallerCertificates;
         private long mVersionCode;
@@ -192,6 +202,20 @@
         }
 
         /**
+         * Set the list of (old and new) certificates used for signing the app to be installed.
+         *
+         * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
+         * lineage/history of the app.
+         *
+         * @see AppInstallMetadata#getAppCertificateLineage()
+         */
+        @NonNull
+        public Builder setAppCertificateLineage(@NonNull List<String> appCertificateLineage) {
+            this.mAppCertificateLineage = Objects.requireNonNull(appCertificateLineage);
+            return this;
+        }
+
+        /**
          * Set name of the installer installing the app.
          *
          * @see AppInstallMetadata#getInstallerName()
@@ -294,6 +318,7 @@
         public AppInstallMetadata build() {
             Objects.requireNonNull(mPackageName);
             Objects.requireNonNull(mAppCertificates);
+            Objects.requireNonNull(mAppCertificateLineage);
             return new AppInstallMetadata(this);
         }
     }
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index e359800..f888813 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -56,6 +56,7 @@
                 PRE_INSTALLED,
                 STAMP_TRUSTED,
                 STAMP_CERTIFICATE_HASH,
+                APP_CERTIFICATE_LINEAGE,
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Key {}
@@ -122,6 +123,13 @@
      */
     public static final int STAMP_CERTIFICATE_HASH = 7;
 
+    /**
+     * SHA-256 of a certificate in the signing lineage of the app.
+     *
+     * <p>Can only be used in {@link StringAtomicFormula}.
+     */
+    public static final int APP_CERTIFICATE_LINEAGE = 8;
+
     public static final int EQ = 0;
     public static final int GT = 1;
     public static final int GTE = 2;
@@ -225,6 +233,11 @@
         }
 
         @Override
+        public boolean isAppCertificateLineageFormula() {
+            return false;
+        }
+
+        @Override
         public boolean isInstallerFormula() {
             return false;
         }
@@ -314,7 +327,8 @@
                             || key == APP_CERTIFICATE
                             || key == INSTALLER_CERTIFICATE
                             || key == INSTALLER_NAME
-                            || key == STAMP_CERTIFICATE_HASH,
+                            || key == STAMP_CERTIFICATE_HASH
+                            || key == APP_CERTIFICATE_LINEAGE,
                     "Key %s cannot be used with StringAtomicFormula", keyToString(key));
             mValue = null;
             mIsHashedValue = null;
@@ -335,7 +349,8 @@
                             || key == APP_CERTIFICATE
                             || key == INSTALLER_CERTIFICATE
                             || key == INSTALLER_NAME
-                            || key == STAMP_CERTIFICATE_HASH,
+                            || key == STAMP_CERTIFICATE_HASH
+                            || key == APP_CERTIFICATE_LINEAGE,
                     "Key %s cannot be used with StringAtomicFormula", keyToString(key));
             mValue = value;
             mIsHashedValue = isHashed;
@@ -348,8 +363,9 @@
          * <p>The value will be automatically hashed with SHA256 and the hex digest will be computed
          * when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32 characters.
          *
-         * <p>The APP_CERTIFICATES, INSTALLER_CERTIFICATES, and STAMP_CERTIFICATE_HASH are always
-         * delivered in hashed form. So the isHashedValue is set to true by default.
+         * <p>The APP_CERTIFICATES, INSTALLER_CERTIFICATES, STAMP_CERTIFICATE_HASH and
+         * APP_CERTIFICATE_LINEAGE are always delivered in hashed form. So the isHashedValue is set
+         * to true by default.
          *
          * @throws IllegalArgumentException if {@code key} cannot be used with string value.
          */
@@ -360,13 +376,15 @@
                             || key == APP_CERTIFICATE
                             || key == INSTALLER_CERTIFICATE
                             || key == INSTALLER_NAME
-                            || key == STAMP_CERTIFICATE_HASH,
+                            || key == STAMP_CERTIFICATE_HASH
+                            || key == APP_CERTIFICATE_LINEAGE,
                     "Key %s cannot be used with StringAtomicFormula", keyToString(key));
             mValue = hashValue(key, value);
             mIsHashedValue =
                     (key == APP_CERTIFICATE
                                     || key == INSTALLER_CERTIFICATE
-                                    || key == STAMP_CERTIFICATE_HASH)
+                                    || key == STAMP_CERTIFICATE_HASH
+                                    || key == APP_CERTIFICATE_LINEAGE)
                             || !mValue.equals(value);
         }
 
@@ -409,6 +427,11 @@
         }
 
         @Override
+        public boolean isAppCertificateLineageFormula() {
+            return getKey() == APP_CERTIFICATE_LINEAGE;
+        }
+
+        @Override
         public boolean isInstallerFormula() {
             return getKey() == INSTALLER_NAME || getKey() == INSTALLER_CERTIFICATE;
         }
@@ -474,6 +497,8 @@
                     return Collections.singletonList(appInstallMetadata.getInstallerName());
                 case AtomicFormula.STAMP_CERTIFICATE_HASH:
                     return Collections.singletonList(appInstallMetadata.getStampCertificateHash());
+                case AtomicFormula.APP_CERTIFICATE_LINEAGE:
+                    return appInstallMetadata.getAppCertificateLineage();
                 default:
                     throw new IllegalStateException(
                             "Unexpected key in StringAtomicFormula: " + key);
@@ -577,6 +602,11 @@
         }
 
         @Override
+        public boolean isAppCertificateLineageFormula() {
+            return false;
+        }
+
+        @Override
         public boolean isInstallerFormula() {
             return false;
         }
@@ -660,6 +690,8 @@
                 return "STAMP_TRUSTED";
             case STAMP_CERTIFICATE_HASH:
                 return "STAMP_CERTIFICATE_HASH";
+            case APP_CERTIFICATE_LINEAGE:
+                return "APP_CERTIFICATE_LINEAGE";
             default:
                 throw new IllegalArgumentException("Unknown key " + key);
         }
@@ -686,6 +718,7 @@
                 || key == INSTALLER_CERTIFICATE
                 || key == PRE_INSTALLED
                 || key == STAMP_TRUSTED
-                || key == STAMP_CERTIFICATE_HASH;
+                || key == STAMP_CERTIFICATE_HASH
+                || key == APP_CERTIFICATE_LINEAGE;
     }
 }
diff --git a/core/java/android/content/integrity/CompoundFormula.java b/core/java/android/content/integrity/CompoundFormula.java
index 1ffabd0..23ee1d99 100644
--- a/core/java/android/content/integrity/CompoundFormula.java
+++ b/core/java/android/content/integrity/CompoundFormula.java
@@ -137,6 +137,11 @@
     }
 
     @Override
+    public boolean isAppCertificateLineageFormula() {
+        return getFormulas().stream().anyMatch(formula -> formula.isAppCertificateLineageFormula());
+    }
+
+    @Override
     public boolean isInstallerFormula() {
         return getFormulas().stream().anyMatch(formula -> formula.isInstallerFormula());
     }
diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
index 9d37299..5bcbef6 100644
--- a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
+++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
@@ -73,6 +73,11 @@
     }
 
     @Override
+    public boolean isAppCertificateLineageFormula() {
+        return false;
+    }
+
+    @Override
     public boolean isInstallerFormula() {
         return true;
     }
diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java
index d965ef5..9e2a332 100644
--- a/core/java/android/content/integrity/IntegrityFormula.java
+++ b/core/java/android/content/integrity/IntegrityFormula.java
@@ -49,14 +49,23 @@
         }
 
         /**
-         * Returns an integrity formula that checks if the app certificates contain {@code
-         * appCertificate}.
+         * Returns an integrity formula that checks if the app certificates contain the string
+         * provided by the appCertificate parameter.
          */
         @NonNull
         public static IntegrityFormula certificatesContain(@NonNull String appCertificate) {
             return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCertificate);
         }
 
+        /**
+         * Returns an integrity formula that checks if the app certificate lineage contains the
+         * string provided by the appCertificate parameter.
+         */
+        @NonNull
+        public static IntegrityFormula certificateLineageContains(@NonNull String appCertificate) {
+            return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE, appCertificate);
+        }
+
         /** Returns an integrity formula that checks the equality to a version code. */
         @NonNull
         public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) {
@@ -187,6 +196,14 @@
     public abstract boolean isAppCertificateFormula();
 
     /**
+     * Returns true when the formula (or one of its atomic formulas) has app certificate lineage as
+     * key.
+     *
+     * @hide
+     */
+    public abstract boolean isAppCertificateLineageFormula();
+
+    /**
      * Returns true when the formula (or one of its atomic formulas) has installer package name or
      * installer certificate as key.
      *
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 8bea006..0673b3a 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -18,6 +18,8 @@
 
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.Activity;
 import android.app.compat.CompatChanges;
@@ -35,10 +37,16 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Printer;
 
+import com.android.internal.util.Parcelling;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Set;
 
 /**
  * Information you can retrieve about a particular application
@@ -48,6 +56,9 @@
  */
 public class ActivityInfo extends ComponentInfo implements Parcelable {
 
+    private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
+            Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForStringSet.class);
+
      // NOTE: When adding new data members be sure to update the copy-constructor, Parcel
      // constructor, and writeToParcel.
 
@@ -525,6 +536,13 @@
     public static final int FLAG_PREFER_MINIMAL_POST_PROCESSING = 0x2000000;
 
     /**
+     * Bit in {@link #flags}: If set, indicates that the activity can be embedded by untrusted
+     * hosts. In this case the interactions with and visibility of the embedded activity may be
+     * limited. Set from the {@link android.R.attr#allowUntrustedActivityEmbedding} attribute.
+     */
+    public static final int FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING = 0x10000000;
+
+    /**
      * @hide Bit in {@link #flags}: If set, this component will only be seen
      * by the system user.  Only works with broadcast receivers.  Set from the
      * android.R.attr#systemUserOnly attribute.
@@ -561,7 +579,8 @@
      * {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
      * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY},
      * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS},
-     * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}.
+     * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER},
+     * {@link #FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING}.
      */
     public int flags;
 
@@ -1080,6 +1099,13 @@
     private static final long CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW = 197654537L;
 
     /**
+     * Optional set of a certificates identifying apps that are allowed to embed this activity. From
+     * the "knownActivityEmbeddingCerts" attribute.
+     */
+    @Nullable
+    private Set<String> mKnownActivityEmbeddingCerts;
+
+    /**
      * Convert Java change bits to native.
      *
      * @hide
@@ -1227,6 +1253,7 @@
         launchMode = orig.launchMode;
         documentLaunchMode = orig.documentLaunchMode;
         permission = orig.permission;
+        mKnownActivityEmbeddingCerts = orig.mKnownActivityEmbeddingCerts;
         taskAffinity = orig.taskAffinity;
         targetActivity = orig.targetActivity;
         flags = orig.flags;
@@ -1442,6 +1469,31 @@
         return mMinAspectRatio;
     }
 
+    /**
+     * Gets the trusted host certificate digests of apps that are allowed to embed this activity.
+     * The digests are computed using the SHA-256 digest algorithm.
+     * @see android.R.attr#knownActivityEmbeddingCerts
+     */
+    @NonNull
+    public Set<String> getKnownActivityEmbeddingCerts() {
+        return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
+                : mKnownActivityEmbeddingCerts;
+    }
+
+    /**
+     * Sets the trusted host certificates of apps that are allowed to embed this activity.
+     * @see #getKnownActivityEmbeddingCerts()
+     * @hide
+     */
+    public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) {
+        // Convert the provided digest to upper case for consistent Set membership
+        // checks when verifying the signing certificate digests of requesting apps.
+        mKnownActivityEmbeddingCerts = new ArraySet<>();
+        for (String knownCert : knownActivityEmbeddingCerts) {
+            mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
+        }
+    }
+
     private boolean isChangeEnabled(long changeId) {
         return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
                 UserHandle.getUserHandleForUid(applicationInfo.uid));
@@ -1573,6 +1625,9 @@
         if (supportsSizeChanges) {
             pw.println(prefix + "supportsSizeChanges=true");
         }
+        if (mKnownActivityEmbeddingCerts != null) {
+            pw.println(prefix + "knownActivityEmbeddingCerts=" + mKnownActivityEmbeddingCerts);
+        }
         super.dumpBack(pw, prefix, dumpFlags);
     }
 
@@ -1618,6 +1673,7 @@
         dest.writeFloat(mMaxAspectRatio);
         dest.writeFloat(mMinAspectRatio);
         dest.writeBoolean(supportsSizeChanges);
+        sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
     }
 
     /**
@@ -1739,6 +1795,10 @@
         mMaxAspectRatio = source.readFloat();
         mMinAspectRatio = source.readFloat();
         supportsSizeChanges = source.readBoolean();
+        mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source);
+        if (mKnownActivityEmbeddingCerts.isEmpty()) {
+            mKnownActivityEmbeddingCerts = null;
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 567f649..2528e16 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -35,6 +35,7 @@
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Printer;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
@@ -52,7 +53,9 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
+import java.util.Set;
 import java.util.UUID;
 
 /**
@@ -62,6 +65,8 @@
  */
 public class ApplicationInfo extends PackageItemInfo implements Parcelable {
     private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
+    private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
+            Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForStringSet.class);
 
     /**
      * Default task affinity of all activities in this application. See
@@ -1550,6 +1555,13 @@
      */
     private int localeConfigRes;
 
+    /**
+     * Optional set of a certificates identifying apps that are allowed to embed activities of this
+     * application. From the "knownActivityEmbeddingCerts" attribute.
+     */
+    @Nullable
+    private Set<String> mKnownActivityEmbeddingCerts;
+
     public void dump(Printer pw, String prefix) {
         dump(pw, prefix, DUMP_FLAG_ALL);
     }
@@ -1673,6 +1685,9 @@
             }
         }
         pw.println(prefix + "createTimestamp=" + createTimestamp);
+        if (mKnownActivityEmbeddingCerts != null) {
+            pw.println(prefix + "knownActivityEmbeddingCerts=" + mKnownActivityEmbeddingCerts);
+        }
         super.dumpBack(pw, prefix);
     }
 
@@ -1787,6 +1802,11 @@
             }
             proto.end(detailToken);
         }
+        if (!ArrayUtils.isEmpty(mKnownActivityEmbeddingCerts)) {
+            for (String knownCert : mKnownActivityEmbeddingCerts) {
+                proto.write(ApplicationInfoProto.KNOWN_ACTIVITY_EMBEDDING_CERTS, knownCert);
+            }
+        }
         proto.end(token);
     }
 
@@ -1837,6 +1857,7 @@
         super(orig);
         taskAffinity = orig.taskAffinity;
         permission = orig.permission;
+        mKnownActivityEmbeddingCerts = orig.mKnownActivityEmbeddingCerts;
         processName = orig.processName;
         className = orig.className;
         theme = orig.theme;
@@ -2006,6 +2027,7 @@
             }
         }
         dest.writeInt(localeConfigRes);
+        sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -2102,6 +2124,10 @@
             }
         }
         localeConfigRes = source.readInt();
+        mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source);
+        if (mKnownActivityEmbeddingCerts.isEmpty()) {
+            mKnownActivityEmbeddingCerts = null;
+        }
     }
 
     /**
@@ -2658,7 +2684,6 @@
         return localeConfigRes;
     }
 
-
     /**
      *  List of all shared libraries this application is linked against. This
      *  list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
@@ -2675,4 +2700,29 @@
         return sharedLibraryInfos;
     }
 
+    /**
+     * Gets the trusted host certificate digests of apps that are allowed to embed activities of
+     * this application. The digests are computed using the SHA-256 digest algorithm.
+     * @see android.R.attr#knownActivityEmbeddingCerts
+     */
+    @NonNull
+    public Set<String> getKnownActivityEmbeddingCerts() {
+        return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
+                : mKnownActivityEmbeddingCerts;
+    }
+
+    /**
+     * Sets the trusted host certificates of apps that are allowed to embed activities of this
+     * application.
+     * @see #getKnownActivityEmbeddingCerts()
+     * @hide
+     */
+    public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) {
+        // Convert the provided digest to upper case for consistent Set membership
+        // checks when verifying the signing certificate digests of requesting apps.
+        mKnownActivityEmbeddingCerts = new ArraySet<>();
+        for (String knownCert : knownActivityEmbeddingCerts) {
+            mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
+        }
+    }
 }
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index cb8988e..08cfbf7 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -58,7 +58,7 @@
     void startActivityAsUser(in IApplicationThread caller, String callingPackage,
             String callingFeatureId, in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
-    PendingIntent getActivityLaunchIntent(in ComponentName component, in Bundle opts,
+    PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
             in UserHandle user);
     void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
             String callingFeatureId, in ComponentName component, in Rect sourceBounds,
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index e35c2f4..301d1bbc 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -749,24 +749,29 @@
     }
 
     /**
-     * Returns a PendingIntent that would start the same activity started from
-     * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.
+     * Returns a mutable PendingIntent that would start the same activity started from
+     * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.  The caller needs to
+     * take care in ensuring that the mutable intent returned is not passed to untrusted parties.
      *
      * @param component The ComponentName of the activity to launch
      * @param startActivityOptions This parameter is no longer supported
      * @param user The UserHandle of the profile
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
     @Nullable
     public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component,
             @Nullable Bundle startActivityOptions, @NonNull UserHandle user) {
+        if (mContext.checkSelfPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
+                != PackageManager.PERMISSION_GRANTED) {
+            Log.w(TAG, "Only allowed for recents.");
+        }
         logErrorForInvalidProfileAccess(user);
         if (DEBUG) {
             Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user);
         }
         try {
-            // due to b/209607104, startActivityOptions will be ignored
-            return mService.getActivityLaunchIntent(component, null /* opts */, user);
+            return mService.getActivityLaunchIntent(mContext.getPackageName(), component, user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ce549c3..4279d07 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4342,6 +4342,21 @@
             = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
 
     /**
+     * Indicates that the package requesting permissions has legacy access for some permissions,
+     * or had it, but it was recently revoked. These request dialogs may show different text,
+     * indicating that the app is requesting continued access to a permission. Will be cleared
+     * from any permission request intent, if set by a non-system server app.
+     * <p>
+     * <strong>Type:</strong> String[]
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES
+            = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
+
+    /**
      * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
      * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}.  This extra names the package which provides
      * the existing definition for the permission.
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 7dbfd08..8e8aaf1 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -532,7 +532,7 @@
      * app.
      *
      * <p><b>Note:</b> See also the support library counterpart
-     * {@link android.support.v4.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported(
+     * {@link androidx.core.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported(
      * Context)}, which supports Android versions lower than {@link VERSION_CODES#O} using the
      * legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}.
      *
@@ -561,7 +561,7 @@
      * previous requests.
      *
      * <p><b>Note:</b> See also the support library counterpart
-     * {@link android.support.v4.content.pm.ShortcutManagerCompat#requestPinShortcut(
+     * {@link androidx.core.content.pm.ShortcutManagerCompat#requestPinShortcut(
      * Context, ShortcutInfoCompat, IntentSender)},
      * which supports Android versions lower than {@link VERSION_CODES#O} using the
      * legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}.
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 515a009..ac1bcf3 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -131,7 +131,8 @@
     /** Usage: The buffer will be written to by the GPU */
     public static final long USAGE_GPU_COLOR_OUTPUT       = 1 << 9;
     /**
-     * The buffer will be used as a composer HAL overlay layer.
+     * The buffer will be used as a hardware composer overlay layer. That is, it will be displayed
+     * using the system compositor via {@link SurfaceControl}
      *
      * This flag is currently only needed when using
      * {@link android.view.SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index f67a5b4..8093764 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -264,6 +264,44 @@
     public @interface StreamUseCase {};
 
     /**
+     * Automatic mirroring based on camera facing
+     *
+     * <p>This is the default mirroring mode for the camera device. With this mode,
+     * the camera output is mirrored horizontally for front-facing cameras. There is
+     * no mirroring for rear-facing and external cameras.</p>
+     */
+    public static final int MIRROR_MODE_AUTO = 0;
+
+    /**
+     * No mirror transform is applied
+     *
+     * <p>No mirroring is applied to the camera output regardless of the camera facing.</p>
+     */
+    public static final int MIRROR_MODE_NONE = 1;
+
+    /**
+     * Camera output is mirrored horizontally
+     *
+     * <p>The camera output is mirrored horizontally, the same behavior as in AUTO mode for
+     * front facing camera.</p>
+     */
+    public static final int MIRROR_MODE_H = 2;
+
+    /**
+     * Camera output is mirrored vertically
+     */
+    public static final int MIRROR_MODE_V = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"MIRROR_MODE_"}, value =
+        {MIRROR_MODE_AUTO,
+          MIRROR_MODE_NONE,
+          MIRROR_MODE_H,
+          MIRROR_MODE_V})
+    public @interface MirrorMode {};
+
+    /**
      * Create a new {@link OutputConfiguration} instance with a {@link Surface}.
      *
      * @param surface
@@ -461,6 +499,7 @@
         mDynamicRangeProfile = DynamicRangeProfiles.STANDARD;
         mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
         mTimestampBase = TIMESTAMP_BASE_DEFAULT;
+        mMirrorMode = MIRROR_MODE_AUTO;
     }
 
     /**
@@ -945,6 +984,42 @@
     }
 
     /**
+     * Set the mirroring mode for this output target
+     *
+     * <p>If this function is not called, the mirroring mode for this output is
+     * {@link #MIRROR_MODE_AUTO}, with which the camera API will mirror the output images
+     * horizontally for front facing camera.</p>
+     *
+     * <p>For efficiency, the mirror effect is applied as a transform flag, so it is only effective
+     * in some outputs. It works automatically for SurfaceView and TextureView outputs. For manual
+     * use of SurfaceTexture, it is reflected in the value of
+     * {@link android.graphics.SurfaceTexture#getTransformMatrix}. For other end points, such as
+     * ImageReader, MediaRecorder, or MediaCodec, the mirror mode has no effect. If mirroring is
+     * needed for such outputs, the application needs to mirror the image buffers itself before
+     * passing them onward.</p>
+     */
+    public void setMirrorMode(@MirrorMode int mirrorMode) {
+        // Verify that the value is in range
+        if (mirrorMode < MIRROR_MODE_AUTO ||
+                mirrorMode > MIRROR_MODE_V) {
+            throw new IllegalArgumentException("Not a valid mirror mode " + mirrorMode);
+        }
+        mMirrorMode = mirrorMode;
+    }
+
+    /**
+     * Get the current mirroring mode
+     *
+     * <p>If no {@link #setMirrorMode} is called first, this function returns
+     * {@link #MIRROR_MODE_AUTO}.</p>
+     *
+     * @return The currently set mirroring mode
+     */
+    public @MirrorMode int getMirrorMode() {
+        return mMirrorMode;
+    }
+
+    /**
      * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
      * instance.
      *
@@ -973,6 +1048,7 @@
         this.mDynamicRangeProfile = other.mDynamicRangeProfile;
         this.mStreamUseCase = other.mStreamUseCase;
         this.mTimestampBase = other.mTimestampBase;
+        this.mMirrorMode = other.mMirrorMode;
     }
 
     /**
@@ -998,6 +1074,8 @@
         DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile);
 
         int timestampBase = source.readInt();
+        int mirrorMode = source.readInt();
+
         mSurfaceGroupId = surfaceSetId;
         mRotation = rotation;
         mSurfaces = surfaces;
@@ -1023,6 +1101,7 @@
         mDynamicRangeProfile = dynamicRangeProfile;
         mStreamUseCase = streamUseCase;
         mTimestampBase = timestampBase;
+        mMirrorMode = mirrorMode;
     }
 
     /**
@@ -1141,6 +1220,7 @@
         dest.writeInt(mDynamicRangeProfile);
         dest.writeInt(mStreamUseCase);
         dest.writeInt(mTimestampBase);
+        dest.writeInt(mMirrorMode);
     }
 
     /**
@@ -1173,7 +1253,8 @@
                     !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) ||
                     mIsMultiResolution != other.mIsMultiResolution ||
                     mStreamUseCase != other.mStreamUseCase ||
-                    mTimestampBase != other.mTimestampBase)
+                    mTimestampBase != other.mTimestampBase ||
+                    mMirrorMode != other.mMirrorMode)
                 return false;
             if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) {
                 return false;
@@ -1211,7 +1292,7 @@
                     mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0,
                     mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
                     mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
-                    mDynamicRangeProfile, mStreamUseCase, mTimestampBase);
+                    mDynamicRangeProfile, mStreamUseCase, mTimestampBase, mMirrorMode);
         }
 
         return HashCodeHelpers.hashCode(
@@ -1220,7 +1301,8 @@
                 mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0,
                 mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
                 mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
-                mDynamicRangeProfile, mStreamUseCase, mTimestampBase);
+                mDynamicRangeProfile, mStreamUseCase, mTimestampBase,
+                mMirrorMode);
     }
 
     private static final String TAG = "OutputConfiguration";
@@ -1258,4 +1340,6 @@
     private int mStreamUseCase;
     // Timestamp base
     private int mTimestampBase;
+    // Mirroring mode
+    private int mMirrorMode;
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index eefa1d3..623f38e 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1214,17 +1214,6 @@
     }
 
     /**
-     * Returns whether the specified display supports DISPLAY_DECORATION.
-     *
-     * @param displayId The display to query support.
-     *
-     * @hide
-     */
-    public boolean getDisplayDecorationSupport(int displayId) {
-        return mGlobal.getDisplayDecorationSupport(displayId);
-    }
-
-    /**
      * Returns the user preference for "Match content frame rate".
      * <p>
      * Never: Even if the app requests it, the device will never try to match its output to the
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index af8ec27..7e7a648 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -32,6 +32,7 @@
 import android.graphics.ColorSpace;
 import android.graphics.Point;
 import android.hardware.display.DisplayManager.DisplayListener;
+import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.media.projection.IMediaProjection;
 import android.media.projection.MediaProjection;
 import android.os.Handler;
@@ -812,13 +813,13 @@
     }
 
     /**
-     * Report whether the display supports DISPLAY_DECORATION.
+     * Report whether/how the display supports DISPLAY_DECORATION.
      *
      * @param displayId The display whose support is being queried.
      *
      * @hide
      */
-    public boolean getDisplayDecorationSupport(int displayId) {
+    public DisplayDecorationSupport getDisplayDecorationSupport(int displayId) {
         try {
             return mDm.getDisplayDecorationSupport(displayId);
         } catch (RemoteException ex) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b3af52b..ddd18f4 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -22,6 +22,7 @@
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.Curve;
+import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
@@ -183,5 +184,5 @@
     int getRefreshRateSwitchingType();
 
     // Query for DISPLAY_DECORATION support.
-    boolean getDisplayDecorationSupport(int displayId);
+    DisplayDecorationSupport getDisplayDecorationSupport(int displayId);
 }
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
index d875156..26d74df 100644
--- a/core/java/android/hardware/input/VirtualKeyEvent.java
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -114,9 +114,56 @@
         }
 
         /**
-         * Sets the Android key code of the event. The set of allowed characters include digits 0-9,
-         * characters A-Z, and standard punctuation, as well as numpad keys, function keys F1-F12,
-         * and meta keys (caps lock, shift, etc.).
+         * Sets the Android key code of the event. The set of allowed keys include digits
+         *              {@link android.view.KeyEvent#KEYCODE_0} through
+         *              {@link android.view.KeyEvent#KEYCODE_9}, characters
+         *              {@link android.view.KeyEvent#KEYCODE_A} through
+         *              {@link android.view.KeyEvent#KEYCODE_Z}, function keys
+         *              {@link android.view.KeyEvent#KEYCODE_F1} through
+         *              {@link android.view.KeyEvent#KEYCODE_F12}, numpad keys
+         *              {@link android.view.KeyEvent#KEYCODE_NUMPAD_0} through
+         *              {@link android.view.KeyEvent#KEYCODE_NUMPAD_RIGHT_PAREN},
+         *              and these additional keys:
+         *              {@link android.view.KeyEvent#KEYCODE_GRAVE}
+         *              {@link android.view.KeyEvent#KEYCODE_MINUS}
+         *              {@link android.view.KeyEvent#KEYCODE_EQUALS}
+         *              {@link android.view.KeyEvent#KEYCODE_LEFT_BRACKET}
+         *              {@link android.view.KeyEvent#KEYCODE_RIGHT_BRACKET}
+         *              {@link android.view.KeyEvent#KEYCODE_BACKSLASH}
+         *              {@link android.view.KeyEvent#KEYCODE_SEMICOLON}
+         *              {@link android.view.KeyEvent#KEYCODE_APOSTROPHE}
+         *              {@link android.view.KeyEvent#KEYCODE_COMMA}
+         *              {@link android.view.KeyEvent#KEYCODE_PERIOD}
+         *              {@link android.view.KeyEvent#KEYCODE_SLASH}
+         *              {@link android.view.KeyEvent#KEYCODE_ALT_LEFT}
+         *              {@link android.view.KeyEvent#KEYCODE_ALT_RIGHT}
+         *              {@link android.view.KeyEvent#KEYCODE_CTRL_LEFT}
+         *              {@link android.view.KeyEvent#KEYCODE_CTRL_RIGHT}
+         *              {@link android.view.KeyEvent#KEYCODE_SHIFT_LEFT}
+         *              {@link android.view.KeyEvent#KEYCODE_SHIFT_RIGHT}
+         *              {@link android.view.KeyEvent#KEYCODE_META_LEFT}
+         *              {@link android.view.KeyEvent#KEYCODE_META_RIGHT}
+         *              {@link android.view.KeyEvent#KEYCODE_CAPS_LOCK}
+         *              {@link android.view.KeyEvent#KEYCODE_SCROLL_LOCK}
+         *              {@link android.view.KeyEvent#KEYCODE_NUM_LOCK}
+         *              {@link android.view.KeyEvent#KEYCODE_ENTER}
+         *              {@link android.view.KeyEvent#KEYCODE_TAB}
+         *              {@link android.view.KeyEvent#KEYCODE_SPACE}
+         *              {@link android.view.KeyEvent#KEYCODE_DPAD_DOWN}
+         *              {@link android.view.KeyEvent#KEYCODE_DPAD_UP}
+         *              {@link android.view.KeyEvent#KEYCODE_DPAD_LEFT}
+         *              {@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT}
+         *              {@link android.view.KeyEvent#KEYCODE_MOVE_END}
+         *              {@link android.view.KeyEvent#KEYCODE_MOVE_HOME}
+         *              {@link android.view.KeyEvent#KEYCODE_PAGE_DOWN}
+         *              {@link android.view.KeyEvent#KEYCODE_PAGE_UP}
+         *              {@link android.view.KeyEvent#KEYCODE_DEL}
+         *              {@link android.view.KeyEvent#KEYCODE_FORWARD_DEL}
+         *              {@link android.view.KeyEvent#KEYCODE_INSERT}
+         *              {@link android.view.KeyEvent#KEYCODE_ESCAPE}
+         *              {@link android.view.KeyEvent#KEYCODE_BREAK}
+         *              {@link android.view.KeyEvent#KEYCODE_BACK}
+         *              {@link android.view.KeyEvent#KEYCODE_FORWARD}
          *
          * @return this builder, to allow for chaining of calls
          */
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index b0439d0..c363909 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -61,6 +61,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Locale;
 import java.util.UUID;
 
@@ -1576,31 +1577,58 @@
     }
 
     /**
-     *  Additional data conveyed by a {@link KeyphraseRecognitionEvent}
-     *  for a key phrase detection.
-     *
-     * @hide
+     * Additional data conveyed by a {@link KeyphraseRecognitionEvent}
+     * for a key phrase detection.
      */
-    public static class KeyphraseRecognitionExtra implements Parcelable {
-        /** The keyphrase ID */
+    public static final class KeyphraseRecognitionExtra implements Parcelable {
+        /**
+         * The keyphrase ID
+         *
+         * @hide
+         */
         @UnsupportedAppUsage
         public final int id;
 
-        /** Recognition modes matched for this event */
+        /**
+         * Recognition modes matched for this event
+         *
+         * @hide
+         */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public final int recognitionModes;
 
-        /** Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
-         * is not performed */
+        /**
+         * Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
+         * is not performed
+         *
+         * @hide
+         */
         @UnsupportedAppUsage
         public final int coarseConfidenceLevel;
 
-        /** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
-         * be recognized (RecognitionConfig) */
+        /**
+         * Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
+         * be recognized (RecognitionConfig)
+         *
+         * @hide
+         */
         @UnsupportedAppUsage
         @NonNull
         public final ConfidenceLevel[] confidenceLevels;
 
+
+        /**
+         * @hide
+         */
+        @TestApi
+        public KeyphraseRecognitionExtra(int id, @RecognitionModes int recognitionModes,
+                int coarseConfidenceLevel) {
+            this(id, recognitionModes, coarseConfidenceLevel, new ConfidenceLevel[0]);
+        }
+
+        /**
+         * @hide
+         */
         @UnsupportedAppUsage
         public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
                 @Nullable ConfidenceLevel[] confidenceLevels) {
@@ -1611,7 +1639,47 @@
                     confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0];
         }
 
-        public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
+        /**
+         * The keyphrase ID associated with this class' additional data
+         */
+        public int getKeyphraseId() {
+            return id;
+        }
+
+        /**
+         * Recognition modes matched for this event
+         */
+        @RecognitionModes
+        public int getRecognitionModes() {
+            return recognitionModes;
+        }
+
+        /**
+         * Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
+         * is not performed
+         *
+         * <p>The confidence level is expressed in percent (0% -100%).
+         */
+        public int getCoarseConfidenceLevel() {
+            return coarseConfidenceLevel;
+        }
+
+        /**
+         * Detected confidence level for users defined in a keyphrase.
+         *
+         * <p>The confidence level is expressed in percent (0% -100%).
+         *
+         * <p>The user ID is derived from the system ID
+         * {@link android.os.UserHandle#getIdentifier()}.
+         *
+         * @hide
+         */
+        @NonNull
+        public Collection<ConfidenceLevel> getConfidenceLevels() {
+            return Arrays.asList(confidenceLevels);
+        }
+
+        public static final @NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
                 = new Parcelable.Creator<KeyphraseRecognitionExtra>() {
             public KeyphraseRecognitionExtra createFromParcel(Parcel in) {
                 return KeyphraseRecognitionExtra.fromParcel(in);
@@ -1632,7 +1700,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(id);
             dest.writeInt(recognitionModes);
             dest.writeInt(coarseConfidenceLevel);
@@ -1657,21 +1725,28 @@
 
         @Override
         public boolean equals(@Nullable Object obj) {
-            if (this == obj)
+            if (this == obj) {
                 return true;
-            if (obj == null)
+            }
+            if (obj == null) {
                 return false;
-            if (getClass() != obj.getClass())
+            }
+            if (getClass() != obj.getClass()) {
                 return false;
+            }
             KeyphraseRecognitionExtra other = (KeyphraseRecognitionExtra) obj;
-            if (!Arrays.equals(confidenceLevels, other.confidenceLevels))
+            if (!Arrays.equals(confidenceLevels, other.confidenceLevels)) {
                 return false;
-            if (id != other.id)
+            }
+            if (id != other.id) {
                 return false;
-            if (recognitionModes != other.recognitionModes)
+            }
+            if (recognitionModes != other.recognitionModes) {
                 return false;
-            if (coarseConfidenceLevel != other.coarseConfidenceLevel)
+            }
+            if (coarseConfidenceLevel != other.coarseConfidenceLevel) {
                 return false;
+            }
             return true;
         }
 
@@ -1715,7 +1790,7 @@
                     keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
         }
 
-        public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
+        public static final @NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
                 = new Parcelable.Creator<KeyphraseRecognitionEvent>() {
             public KeyphraseRecognitionEvent createFromParcel(Parcel in) {
                 return KeyphraseRecognitionEvent.fromParcelForKeyphrase(in);
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 83b053a..499634a 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -102,11 +102,4 @@
         lp.token = token;
         setAttributes(lp);
     }
-
-    /**
-     * Returns {@code true} if Window was created and added to WM.
-     */
-    boolean isInitialized() {
-        return mIsViewAdded;
-    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index b6e1b1f..adf2759 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -578,6 +578,7 @@
     private boolean mImeSurfaceScheduledForRemoval;
     private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker();
     private boolean mDestroyed;
+    private boolean mOnPreparedStylusHwCalled;
 
     /** Stylus handwriting Ink window.  */
     private InkWindow mInkWindow;
@@ -919,9 +920,10 @@
                 Log.d(TAG, "Input should have started before starting Stylus handwriting.");
                 return;
             }
-            if (!mInkWindow.isInitialized()) {
+            if (!mOnPreparedStylusHwCalled) {
                 // prepare hasn't been called by Stylus HOVER.
                 onPrepareStylusHandwriting();
+                mOnPreparedStylusHwCalled = true;
             }
             if (onStartStylusHandwriting()) {
                 mPrivOps.onStylusHandwritingReady(requestId);
@@ -976,6 +978,7 @@
         public void initInkWindow() {
             mInkWindow.initOnly();
             onPrepareStylusHandwriting();
+            mOnPreparedStylusHwCalled = true;
         }
 
         /**
@@ -2354,7 +2357,7 @@
 
     /**
      * Called to prepare stylus handwriting.
-     * The system calls this before the first {@link #onStartStylusHandwriting} request.
+     * The system calls this before the {@link #onStartStylusHandwriting} request.
      *
      * <p>Note: The system tries to call this as early as possible, when it detects that
      * handwriting stylus input is imminent. However, that a subsequent call to
@@ -2438,6 +2441,7 @@
         mInkWindow.hide(false /* remove */);
 
         mPrivOps.finishStylusHandwriting(requestId);
+        mOnPreparedStylusHwCalled = false;
         onFinishStylusHandwriting();
     }
 
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index c85fcd9..59c9c50 100644
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -217,6 +217,7 @@
             rowEdgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Row_rowEdgeFlags, 0);
             mode = a.getResourceId(com.android.internal.R.styleable.Keyboard_Row_keyboardMode,
                     0);
+            a.recycle();
         }
     }
 
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index b780b21..af63dd3 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -326,9 +326,11 @@
             }
         }
 
+        a.recycle();
         a = mContext.obtainStyledAttributes(
                 com.android.internal.R.styleable.Theme);
         mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
+        a.recycle();
 
         mPreviewPopup = new PopupWindow(context);
         if (previewLayout != 0) {
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index ec752fd..0fd3e03 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -547,7 +547,8 @@
         if (profile.excludeLocalRoutes && !profile.isBypassable) {
             Log.w(TAG, "ExcludeLocalRoutes should only be set in the bypassable VPN");
         }
-        builder.setExcludeLocalRoutes(profile.excludeLocalRoutes && profile.isBypassable);
+
+        builder.setLocalRoutesExcluded(profile.excludeLocalRoutes && profile.isBypassable);
         builder.setRequiresInternetValidation(profile.requiresInternetValidation);
 
         return builder.build();
@@ -1104,7 +1105,7 @@
          */
         @NonNull
         @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
-        public Builder setExcludeLocalRoutes(boolean excludeLocalRoutes) {
+        public Builder setLocalRoutesExcluded(boolean excludeLocalRoutes) {
             mExcludeLocalRoutes = excludeLocalRoutes;
             return this;
         }
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index d8f098e..18ec8f5 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -167,6 +167,8 @@
     public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave";
     /** @hide */
     public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted";
+    /** @hide */
+    public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
 
     private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
 
@@ -174,6 +176,9 @@
     public static final int FOREGROUND_THRESHOLD_STATE =
             ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 
+    /** @hide */
+    public static final int TOP_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_TOP;
+
     /**
      * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
      * applies to.
@@ -245,6 +250,20 @@
      */
     public static final int ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS = 1 << 4;
     /**
+     * Flag to indicate that app is exempt from certain network restrictions because of it being
+     * in the bound top or top procstate.
+     *
+     * @hide
+     */
+    public static final int ALLOWED_REASON_TOP = 1 << 5;
+    /**
+     * Flag to indicate that app is exempt from low power standby restrictions because of it being
+     * allowlisted.
+     *
+     * @hide
+     */
+    public static final int ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST = 1 << 6;
+    /**
      * Flag to indicate that app is exempt from certain metered network restrictions because user
      * explicitly exempted it.
      *
@@ -750,6 +769,14 @@
                 || (capability & ActivityManager.PROCESS_CAPABILITY_NETWORK) != 0;
     }
 
+    /** @hide */
+    public static boolean isProcStateAllowedWhileInLowPowerStandby(@Nullable UidState uidState) {
+        if (uidState == null) {
+            return false;
+        }
+        return uidState.procState <= TOP_THRESHOLD_STATE;
+    }
+
     /**
      * Returns true if {@param procState} is considered foreground and as such will be allowed
      * to access network when the device is in data saver mode. Otherwise, false.
diff --git a/core/java/android/net/PlatformVpnProfile.java b/core/java/android/net/PlatformVpnProfile.java
index 8bd1c8d..c0fb4cf 100644
--- a/core/java/android/net/PlatformVpnProfile.java
+++ b/core/java/android/net/PlatformVpnProfile.java
@@ -83,7 +83,7 @@
     /**
      * Returns whether the local traffic is exempted from the VPN.
      */
-    public final boolean getExcludeLocalRoutes() {
+    public final boolean areLocalRoutesExcluded() {
         return mExcludeLocalRoutes;
     }
 
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 5aad997..779d931 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -52,7 +53,7 @@
  * app (unlike VpnService).
  *
  * <p>VPN apps using supported protocols should preferentially use this API over the {@link
- * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user
+ * VpnService} API for ease-of-development and reduced maintenance burden. This also give the user
  * the guarantee that VPN network traffic is not subjected to on-device packet interception.
  *
  * @see Ikev2VpnProfile
@@ -97,130 +98,173 @@
     public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
 
     /**
-     * Action sent in the intent when an error occurred.
+     * Action sent in {@link android.content.Intent}s to VpnManager clients when an event occurred.
      *
-     * @hide
+     * This action will have a category of either {@link #CATEGORY_EVENT_IKE_ERROR},
+     * {@link #CATEGORY_EVENT_NETWORK_ERROR}, or {@link #CATEGORY_EVENT_DEACTIVATED_BY_USER},
+     * that the app can use to filter events it's interested in reacting to.
+     *
+     * It will also contain the following extras :
+     * <ul>
+     *   <li>{@link #EXTRA_SESSION_KEY}, a {@code String} for the session key, as returned by
+     *       {@link #startProvisionedVpnProfileSession}.
+     *   <li>{@link #EXTRA_TIMESTAMP}, a long for the timestamp at which the error occurred,
+     *       in milliseconds since the epoch, as returned by
+     *       {@link java.lang.System#currentTimeMillis}.
+     *   <li>{@link #EXTRA_UNDERLYING_NETWORK}, a {@link Network} containing the underlying
+     *       network at the time the error occurred, or null if none. Note that this network
+     *       may have disconnected already.
+     *   <li>{@link #EXTRA_UNDERLYING_NETWORK_CAPABILITIES}, a {@link NetworkCapabilities} for
+     *       the underlying network at the time the error occurred.
+     *   <li>{@link #EXTRA_UNDERLYING_LINK_PROPERTIES}, a {@link LinkProperties} for the underlying
+     *       network at the time the error occurred.
+     * </ul>
+     * When this event is an error, either {@link #CATEGORY_EVENT_IKE_ERROR} or
+     * {@link #CATEGORY_EVENT_NETWORK_ERROR}, the following extras will be populated :
+     * <ul>
+     *   <li>{@link #EXTRA_ERROR_CLASS}, an {@code int} for the class of error, either
+     *       {@link #ERROR_CLASS_RECOVERABLE} or {@link #ERROR_CLASS_NOT_RECOVERABLE}.
+     *   <li>{@link #EXTRA_ERROR_CODE}, an {@code int} error code specific to the error. See
+     *       {@link #EXTRA_ERROR_CODE} for details.
+     * </ul>
      */
-    public static final String ACTION_VPN_MANAGER_ERROR = "android.net.action.VPN_MANAGER_ERROR";
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String ACTION_VPN_MANAGER_EVENT = "android.net.action.VPN_MANAGER_EVENT";
 
     /**
-     * An IKE protocol error. Codes are the codes from IkeProtocolException, RFC 7296.
+     * An IKE protocol error occurred.
      *
-     * @hide
+     * Codes (in {@link #EXTRA_ERROR_CODE}) are the codes from
+     * {@link android.net.ipsec.ike.exceptions.IkeProtocolException}, as defined by IANA in
+     * "IKEv2 Notify Message Types - Error Types".
      */
-    public static final String CATEGORY_ERROR_IKE = "android.net.category.ERROR_IKE";
+    @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_EVENT_IKE_ERROR = "android.net.category.EVENT_IKE_ERROR";
 
     /**
-     * User deactivated the VPN, either by turning it off or selecting a different VPN provider.
-     * The error code is always 0.
+     * A network error occurred.
      *
-     * @hide
+     * Error codes (in {@link #EXTRA_ERROR_CODE}) are ERROR_CODE_NETWORK_*.
      */
-    public static final String CATEGORY_ERROR_USER_DEACTIVATED =
-            "android.net.category.ERROR_USER_DEACTIVATED";
+    @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_EVENT_NETWORK_ERROR =
+            "android.net.category.EVENT_NETWORK_ERROR";
 
     /**
-     * Network error. Error codes are ERROR_CODE_NETWORK_*.
+     * The user deactivated the VPN.
      *
-     * @hide
+     * This can happen either when the user turns the VPN off explicitly, or when they select
+     * a different VPN provider.
      */
-    public static final String CATEGORY_ERROR_NETWORK = "android.net.category.ERROR_NETWORK";
+    @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_EVENT_DEACTIVATED_BY_USER =
+            "android.net.category.EVENT_DEACTIVATED_BY_USER";
 
     /**
-     * The key of the session that experienced this error, as returned by
-     * startProvisionedVpnProfileSession.
+     * The key of the session that experienced this event, as a {@code String}.
      *
-     * @hide
+     * This is the same key that was returned by {@link #startProvisionedVpnProfileSession}.
      */
     public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY";
 
     /**
-     * Extra for the Network object that was the underlying network at the time of the failure, or
-     * null if none.
+     * The network that was underlying the VPN when the event occurred, as a {@link Network}.
      *
-     * @hide
+     * This extra will be null if there was no underlying network at the time of the event.
      */
     public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK";
 
     /**
-     * The NetworkCapabilities of the underlying network.
+     * The {@link NetworkCapabilities} of the underlying network when the event occurred.
      *
-     * @hide
+     * This extra will be null if there was no underlying network at the time of the event.
      */
     public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES =
             "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES";
 
     /**
-     * The LinkProperties of the underlying network.
+     * The {@link LinkProperties} of the underlying network when the event occurred.
      *
-     * @hide
+     * This extra will be null if there was no underlying network at the time of the event.
      */
     public static final String EXTRA_UNDERLYING_LINK_PROPERTIES =
             "android.net.extra.UNDERLYING_LINK_PROPERTIES";
 
     /**
-     * A long timestamp with SystemClock.elapsedRealtime base for when the event happened.
+     * A {@code long} timestamp containing the time at which the event occurred.
      *
-     * @hide
+     * This is a number of milliseconds since the epoch, suitable to be compared with
+     * {@link java.lang.System#currentTimeMillis}.
      */
     public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP";
 
     /**
-     * Extra for the error type. This is ERROR_NOT_RECOVERABLE or ERROR_RECOVERABLE.
+     * Extra for the error class, as an {@code int}.
      *
-     * @hide
+     * This is always either {@link #ERROR_CLASS_NOT_RECOVERABLE} or
+     * {@link #ERROR_CLASS_RECOVERABLE}. This extra is only populated for error categories.
      */
-    public static final String EXTRA_ERROR_TYPE = "android.net.extra.ERROR_TYPE";
+    public static final String EXTRA_ERROR_CLASS = "android.net.extra.ERROR_CLASS";
 
     /**
-     * Extra for the error code. The value will be 0 for CATEGORY_ERROR_USER_DEACTIVATED, one of
-     * ERROR_CODE_NETWORK_* for ERROR_CATEGORY_NETWORK or one of values defined in
-     * IkeProtocolException#ErrorType for CATEGORY_ERROR_IKE.
+     * Extra for an error code, as an {@code int}.
      *
-     * @hide
+     * <ul>
+     *   <li>For {@link #CATEGORY_EVENT_NETWORK_ERROR}, this is one of the
+     *       {@code ERROR_CODE_NETWORK_*} constants.
+     *   <li>For {@link #CATEGORY_EVENT_IKE_ERROR}, this is one of values defined in
+     *       {@link android.net.ipsec.ike.exceptions.IkeProtocolException}.ERROR_TYPE_*.
+     * </ul>
+     * For non-error categories, this extra is not populated.
      */
     public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE";
 
     /**
-     * This error is fatal, e.g. the VPN was disabled or configuration error. The stack will not
-     * retry connection.
+     * {@link #EXTRA_ERROR_CLASS} coding for a non-recoverable error.
      *
-     * @hide
+     * This error is fatal, e.g. configuration error. The stack will not retry connection.
      */
-    public static final int ERROR_NOT_RECOVERABLE = 1;
+    public static final int ERROR_CLASS_NOT_RECOVERABLE = 1;
 
     /**
+     * {@link #EXTRA_ERROR_CLASS} coding for a recoverable error.
+     *
      * The stack experienced an error but will retry with exponential backoff, e.g. network timeout.
-     *
-     * @hide
      */
-    public static final int ERROR_RECOVERABLE = 2;
+    public static final int ERROR_CLASS_RECOVERABLE = 2;
 
     /**
-     * An error code to indicate that there was an UnknownHostException.
+     * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} to indicate that the
+     * network host isn't known.
      *
-     * @hide
+     * This happens when domain name resolution could not resolve an IP address for the
+     * specified host. {@see java.net.UnknownHostException}
      */
     public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0;
 
     /**
-     * An error code to indicate that there is a SocketTimeoutException.
+     * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating a timeout.
      *
-     * @hide
+     * For Ikev2 VPNs, this happens typically after a retransmission failure.
+     * {@see android.net.ipsec.ike.exceptions.IkeTimeoutException}
      */
-    public static final int ERROR_CODE_NETWORK_TIMEOUT = 1;
+    public static final int ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT = 1;
 
     /**
-     * An error code to indicate the connection was reset. (e.g. SocketException)
+     * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating that
+     * network connectivity was lost.
      *
-     * @hide
+     * The most common reason for this error is that the underlying network was disconnected,
+     * {@see android.net.ipsec.ike.exceptions.IkeNetworkLostException}.
      */
-    public static final int ERROR_CODE_NETWORK_RESET = 2;
+    public static final int ERROR_CODE_NETWORK_LOST = 2;
 
     /**
-     * An error code to indicate that there is an IOException.
+     * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating an
+     * input/output error.
      *
-     * @hide
+     * This code happens when reading or writing to sockets on the underlying networks was
+     * terminated by an I/O error. {@see IOException}.
      */
     public static final int ERROR_CODE_NETWORK_IO = 3;
 
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index 9772bde..2dd3aaa1 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -24,6 +24,7 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.TAG_NONE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -108,6 +109,7 @@
         static final int VERSION_ADD_METERED = 4;
         static final int VERSION_ADD_DEFAULT_NETWORK = 5;
         static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
+        static final int VERSION_ADD_SUB_ID = 7;
     }
 
     /**
@@ -448,6 +450,13 @@
                 oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO;
             }
 
+            final int subId;
+            if (version >= IdentitySetVersion.VERSION_ADD_SUB_ID) {
+                subId = in.readInt();
+            } else {
+                subId = INVALID_SUBSCRIPTION_ID;
+            }
+
             // Legacy files might contain TYPE_MOBILE_* types which were deprecated in later
             // releases. For backward compatibility, record them as TYPE_MOBILE instead.
             final int collapsedLegacyType = getCollapsedLegacyType(type);
@@ -457,7 +466,8 @@
                     .setWifiNetworkKey(networkId)
                     .setRoaming(roaming).setMetered(metered)
                     .setDefaultNetwork(defaultNetwork)
-                    .setOemManaged(oemNetCapabilities);
+                    .setOemManaged(oemNetCapabilities)
+                    .setSubId(subId);
             if (type == TYPE_MOBILE && ratType != NetworkTemplate.NETWORK_TYPE_ALL) {
                 builder.setRatType(ratType);
             }
@@ -501,10 +511,10 @@
      * This is copied from {@code NetworkStatsCollection#readLegacyUid}.
      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
      *
-     * @param taggedData whether to read tagged data. For legacy uid files, the tagged
-     *                   data was stored in the same binary file with non-tagged data.
-     *                   But in later releases, these data should be kept in different
-     *                   recorders.
+     * @param taggedData whether to read only tagged data (true) or only non-tagged data
+     *                   (false). For legacy uid files, the tagged data was stored in
+     *                   the same binary file with non-tagged data. But in later releases,
+     *                   these data should be kept in different recorders.
      * @hide
      */
     @VisibleForTesting
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9970641..1d39089 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -401,7 +401,12 @@
         /**
          * All known codenames starting from {@link VERSION_CODES.Q}.
          *
-         * <p>This includes in development codenames as well.
+         * <p>This includes in development codenames as well, i.e. if {@link #CODENAME} is not "REL"
+         * then the value of that is present in this set.
+         *
+         * <p>If a particular string is not present in this set, then it is either not a codename
+         * or a codename for a future release. For example, during Android R development, "Tiramisu"
+         * was not a known codename.
          *
          * @hide
          */
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 0257408..3d12941 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -1333,7 +1333,7 @@
         final Context context = AppGlobals.getInitialApplication();
         final int uid = context.getApplicationInfo().uid;
         // Isolated processes and Instant apps are never allowed to be in scoped storage
-        if (Process.isIsolated(uid)) {
+        if (Process.isIsolated(uid) || Process.isSupplemental(uid)) {
             return false;
         }
 
diff --git a/core/java/android/os/FileUriExposedException.java b/core/java/android/os/FileUriExposedException.java
index e47abe2..a3af24d 100644
--- a/core/java/android/os/FileUriExposedException.java
+++ b/core/java/android/os/FileUriExposedException.java
@@ -35,7 +35,7 @@
  * or higher. Applications targeting earlier SDK versions are allowed to share
  * {@code file://} {@link android.net.Uri}, but it's strongly discouraged.
  *
- * @see android.support.v4.content.FileProvider
+ * @see androidx.core.content.FileProvider
  * @see Intent#FLAG_GRANT_READ_URI_PERMISSION
  */
 public class FileUriExposedException extends RuntimeException {
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 2fe0622..17b5ec5 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -929,6 +929,7 @@
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
+    @TestApi
     public static final int toSupplementalUid(int uid) {
         return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID);
     }
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 70aaa5e..412a33a 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -942,7 +942,7 @@
              * <p>Instead, apps should use {@code content://} Uris so the platform can extend
              * temporary permission for the receiving app to access the resource.
              *
-             * @see android.support.v4.content.FileProvider
+             * @see androidx.core.content.FileProvider
              * @see Intent#FLAG_GRANT_READ_URI_PERMISSION
              */
             public @NonNull Builder detectFileUriExposure() {
diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
index 68b5679..02db274 100644
--- a/core/java/android/os/logcat/ILogcatManagerService.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -22,5 +22,7 @@
 interface ILogcatManagerService {
     void startThread(in int uid, in int gid, in int pid, in int fd);
     void finishThread(in int uid, in int gid, in int pid, in int fd);
+    void approve(in int uid, in int gid, in int pid, in int fd);
+    void decline(in int uid, in int gid, in int pid, in int fd);
 }
 
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 052bc6a..4d65f39 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1331,8 +1331,10 @@
 
     /**
      * Return the list of shared/external storage volumes currently available to
-     * the calling user and the user it shares media with
-     * CDD link : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
+     * the calling user and the user it shares media with. Please refer to
+     * <a href="https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support">
+     *     multi-user support</a> for more details.
+     *
      * <p>
      * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also
      * includes the volumes belonging to any user it shares media with
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 12fa0dd..fc7ac11 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -16,6 +16,12 @@
 
 package android.permission;
 
+import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
 import static android.os.Build.VERSION_CODES.S;
 
 import android.Manifest;
@@ -107,6 +113,16 @@
     public static final int PERMISSION_HARD_DENIED = 2;
 
     /**
+     * The set of flags that indicate that a permission state has been explicitly set
+     *
+     * @hide
+     */
+    public static final int EXPLICIT_SET_FLAGS = FLAG_PERMISSION_USER_SET
+            | FLAG_PERMISSION_USER_FIXED | FLAG_PERMISSION_POLICY_FIXED
+            | FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_GRANTED_BY_DEFAULT
+            | FLAG_PERMISSION_GRANTED_BY_ROLE;
+
+    /**
      * Activity action: Launch UI to review permission decisions.
      * <p>
      * <strong>Important:</strong>You must protect the activity that handles this action with the
@@ -1447,6 +1463,7 @@
      * @hide
      */
     @TestApi
+    @RequiresPermission(Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL)
     public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String packageName,
             int userId) {
         try {
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 22399f5..bbee48b 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -142,7 +142,7 @@
      * switch to a new fragment.
      *
      * @deprecated Use {@link
-     * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
+     * androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
      */
     @Deprecated
     public interface OnPreferenceStartFragmentCallback {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index bb1f393..3459172 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -31,6 +31,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -39,9 +44,11 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
+import android.util.Xml;
 import android.view.ActionMode;
 import android.view.Display;
 import android.view.KeyEvent;
@@ -57,9 +64,14 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.internal.R;
 import com.android.internal.util.DumpUtils;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.function.Consumer;
@@ -159,8 +171,9 @@
  * </pre>
  */
 public class DreamService extends Service implements Window.Callback {
-    private final String mTag =
-            DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
+    private static final String TAG = DreamService.class.getSimpleName();
+    private final String mTag = TAG + "[" + getClass().getSimpleName() + "]";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     /**
      * The name of the dream manager service.
@@ -191,6 +204,11 @@
     public static final String DREAM_META_DATA = "android.service.dream";
 
     /**
+     * Name of the root tag under which a Dream defines its metadata in an XML file.
+     */
+    private static final String DREAM_META_DATA_ROOT_TAG = "dream";
+
+    /**
      * Extra containing a boolean for whether to show complications on the overlay.
      * @hide
      */
@@ -239,13 +257,16 @@
             mRequests = new ArrayDeque<>();
         }
 
-        public void bind(Context context, @Nullable ComponentName overlayService) {
+        public void bind(Context context, @Nullable ComponentName overlayService,
+                ComponentName dreamService) {
             if (overlayService == null) {
                 return;
             }
 
             final Intent overlayIntent = new Intent();
             overlayIntent.setComponent(overlayService);
+            overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
+                    fetchShouldShowComplications(context, dreamService));
 
             context.bindService(overlayIntent,
                     this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
@@ -967,7 +988,8 @@
 
         // Connect to the overlay service if present.
         if (!mWindowless) {
-            mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT));
+            mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT),
+                    new ComponentName(this, getClass()));
         }
 
         return mDreamServiceWrapper;
@@ -1081,6 +1103,86 @@
     // end public api
 
     /**
+     * Parses and returns metadata of the dream service indicated by the service info. Returns null
+     * if metadata cannot be found.
+     *
+     * Note that {@link ServiceInfo} must be fetched with {@link PackageManager#GET_META_DATA} flag.
+     *
+     * @hide
+     */
+    @Nullable
+    public static DreamMetadata getDreamMetadata(Context context, ServiceInfo serviceInfo) {
+        final PackageManager pm = context.getPackageManager();
+
+        final TypedArray rawMetadata = readMetadata(pm, serviceInfo);
+        if (rawMetadata == null) return null;
+
+        final DreamMetadata metadata = new DreamMetadata(
+                convertToComponentName(rawMetadata.getString(
+                        com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
+                rawMetadata.getDrawable(
+                        com.android.internal.R.styleable.Dream_previewImage),
+                rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
+                        DEFAULT_SHOW_COMPLICATIONS));
+        rawMetadata.recycle();
+        return metadata;
+    }
+
+    /**
+     * Returns the raw XML metadata fetched from the {@link ServiceInfo}.
+     *
+     * Returns <code>null</code> if the {@link ServiceInfo} doesn't contain valid dream metadata.
+     */
+    @Nullable
+    private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) {
+        if (serviceInfo == null || serviceInfo.metaData == null) {
+            return null;
+        }
+
+        try (XmlResourceParser parser =
+                     serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) {
+            if (parser == null) {
+                if (DEBUG) Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " metadata");
+                return null;
+            }
+
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            while (true) {
+                final int type = parser.next();
+                if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) {
+                    break;
+                }
+            }
+
+            if (!parser.getName().equals(DREAM_META_DATA_ROOT_TAG)) {
+                if (DEBUG) {
+                    Log.w(TAG, "Metadata does not start with " + DREAM_META_DATA_ROOT_TAG + " tag");
+                }
+                return null;
+            }
+
+            return pm.getResourcesForApplication(serviceInfo.applicationInfo).obtainAttributes(
+                    attrs, com.android.internal.R.styleable.Dream);
+        } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
+            if (DEBUG) Log.e(TAG, "Error parsing: " + serviceInfo.packageName, e);
+            return null;
+        }
+    }
+
+    private static ComponentName convertToComponentName(String flattenedString,
+            ServiceInfo serviceInfo) {
+        if (flattenedString == null) {
+            return null;
+        }
+
+        if (!flattenedString.contains("/")) {
+            return new ComponentName(serviceInfo.packageName, flattenedString);
+        }
+
+        return ComponentName.unflattenFromString(flattenedString);
+    }
+
+    /**
      * Called by DreamController.stopDream() when the Dream is about to be unbound and destroyed.
      *
      * Must run on mHandler.
@@ -1242,6 +1344,30 @@
         return (oldFlags&~mask) | (flags&mask);
     }
 
+    /**
+     * Fetches metadata of the dream indicated by the {@link ComponentName}, and returns whether
+     * the dream should show complications on the overlay. If not defined, returns
+     * {@link DreamService#DEFAULT_SHOW_COMPLICATIONS}.
+     */
+    private static boolean fetchShouldShowComplications(Context context,
+            ComponentName componentName) {
+        final PackageManager pm = context.getPackageManager();
+
+        try {
+            final ServiceInfo si = pm.getServiceInfo(componentName,
+                    PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
+            final DreamMetadata metadata = getDreamMetadata(context, si);
+
+            if (metadata != null) {
+                return metadata.showComplications;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DEBUG) Log.w(TAG, "cannot find component " + componentName.flattenToShortString());
+        }
+
+        return DEFAULT_SHOW_COMPLICATIONS;
+    }
+
     @Override
     protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) {
         DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000);
@@ -1302,4 +1428,27 @@
             onWindowCreated(a.getWindow());
         }
     }
+
+    /**
+     * Represents metadata defined in {@link android.R.styleable#Dream &lt;dream&gt;}.
+     *
+     * @hide
+     */
+    public static final class DreamMetadata {
+        @Nullable
+        public final ComponentName settingsActivity;
+
+        @Nullable
+        public final Drawable previewImage;
+
+        @NonNull
+        public final boolean showComplications;
+
+        DreamMetadata(ComponentName settingsActivity, Drawable previewImage,
+                boolean showComplications) {
+            this.settingsActivity = settingsActivity;
+            this.previewImage = previewImage;
+            this.showComplications = showComplications;
+        }
+    }
 }
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java
index 1922607..01d5638 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractHotwordDetector.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
@@ -34,6 +35,9 @@
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
 /** Base implementation of {@link HotwordDetector}. */
 abstract class AbstractHotwordDetector implements HotwordDetector {
     private static final String TAG = AbstractHotwordDetector.class.getSimpleName();
@@ -45,6 +49,8 @@
     private final Handler mHandler;
     private final HotwordDetector.Callback mCallback;
     private final int mDetectorType;
+    private Consumer<AbstractHotwordDetector> mOnDestroyListener;
+    private final AtomicBoolean mIsDetectorActive;
 
     AbstractHotwordDetector(
             IVoiceInteractionManagerService managerService,
@@ -55,6 +61,7 @@
         mHandler = new Handler(Looper.getMainLooper());
         mCallback = callback;
         mDetectorType = detectorType;
+        mIsDetectorActive = new AtomicBoolean(true);
     }
 
     /**
@@ -70,6 +77,7 @@
         if (DEBUG) {
             Slog.i(TAG, "#recognizeHotword");
         }
+        throwIfDetectorIsNoLongerActive();
 
         // TODO: consider closing existing session.
 
@@ -106,6 +114,7 @@
         if (DEBUG) {
             Slog.d(TAG, "updateState()");
         }
+        throwIfDetectorIsNoLongerActive();
         synchronized (mLock) {
             updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType);
         }
@@ -126,6 +135,35 @@
         }
     }
 
+    void registerOnDestroyListener(Consumer<AbstractHotwordDetector> onDestroyListener) {
+        synchronized (mLock) {
+            if (mOnDestroyListener != null) {
+                throw new IllegalStateException("only one destroy listener can be registered");
+            }
+            mOnDestroyListener = onDestroyListener;
+        }
+    }
+
+    @CallSuper
+    @Override
+    public void destroy() {
+        if (!mIsDetectorActive.get()) {
+            return;
+        }
+        mIsDetectorActive.set(false);
+        synchronized (mLock) {
+            mOnDestroyListener.accept(this);
+        }
+    }
+
+    protected void throwIfDetectorIsNoLongerActive() {
+        if (!mIsDetectorActive.get()) {
+            Slog.e(TAG, "attempting to use a destroyed detector which is no longer active");
+            throw new IllegalStateException(
+                    "attempting to use a destroyed detector which is no longer active");
+        }
+    }
+
     private static class BinderCallback
             extends IMicrophoneHotwordDetectionVoiceInteractionCallback.Stub {
         private final Handler mHandler;
@@ -146,7 +184,10 @@
             mHandler.sendMessage(obtainMessage(
                     HotwordDetector.Callback::onDetected,
                     mCallback,
-                    new AlwaysOnHotwordDetector.EventPayload(audioFormat, hotwordDetectedResult)));
+                    new AlwaysOnHotwordDetector.EventPayload.Builder()
+                            .setCaptureAudioFormat(audioFormat)
+                            .setHotwordDetectedResult(hotwordDetectedResult)
+                            .build()));
         }
     }
 }
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index c9daf52..bec5d1b 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
@@ -59,6 +60,9 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -336,7 +340,39 @@
      * Additional payload for {@link Callback#onDetected}.
      */
     public static class EventPayload {
-        private final boolean mTriggerAvailable;
+
+        /**
+         * Flags for describing the data format provided in the event payload.
+         *
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"DATA_FORMAT_"}, value = {
+                DATA_FORMAT_RAW,
+                DATA_FORMAT_TRIGGER_AUDIO,
+        })
+        public @interface DataFormat {
+        }
+
+        /**
+         * Data format is not strictly defined by the framework, and the
+         * {@link android.hardware.soundtrigger.SoundTriggerModule} voice engine may populate this
+         * field in any format.
+         */
+        public static final int DATA_FORMAT_RAW = 0;
+
+        /**
+         * Data format is defined as trigger audio.
+         *
+         * <p>When this format is used, {@link #getCaptureAudioFormat()} can be used to understand
+         * further the audio format for reading the data.
+         *
+         * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+         */
+        public static final int DATA_FORMAT_TRIGGER_AUDIO = 1;
+
+        @DataFormat
+        private final int mDataFormat;
         // Indicates if {@code captureSession} can be used to continue capturing more audio
         // from the DSP hardware.
         private final boolean mCaptureAvailable;
@@ -348,40 +384,24 @@
         private final byte[] mData;
         private final HotwordDetectedResult mHotwordDetectedResult;
         private final ParcelFileDescriptor mAudioStream;
+        private final List<KeyphraseRecognitionExtra> mKephraseExtras;
 
-        EventPayload(boolean triggerAvailable, boolean captureAvailable,
-                AudioFormat audioFormat, int captureSession, byte[] data) {
-            this(triggerAvailable, captureAvailable, audioFormat, captureSession, data, null,
-                    null);
-        }
-
-        EventPayload(boolean triggerAvailable, boolean captureAvailable,
-                AudioFormat audioFormat, int captureSession, byte[] data,
-                HotwordDetectedResult hotwordDetectedResult) {
-            this(triggerAvailable, captureAvailable, audioFormat, captureSession, data,
-                    hotwordDetectedResult, null);
-        }
-
-        EventPayload(AudioFormat audioFormat, HotwordDetectedResult hotwordDetectedResult) {
-            this(false, false, audioFormat, -1, null, hotwordDetectedResult, null);
-        }
-
-        EventPayload(AudioFormat audioFormat,
-                HotwordDetectedResult hotwordDetectedResult,
-                ParcelFileDescriptor audioStream) {
-            this(false, false, audioFormat, -1, null, hotwordDetectedResult, audioStream);
-        }
-
-        private EventPayload(boolean triggerAvailable, boolean captureAvailable,
-                AudioFormat audioFormat, int captureSession, byte[] data,
-                HotwordDetectedResult hotwordDetectedResult, ParcelFileDescriptor audioStream) {
-            mTriggerAvailable = triggerAvailable;
+        private EventPayload(boolean captureAvailable,
+                @Nullable AudioFormat audioFormat,
+                int captureSession,
+                @DataFormat int dataFormat,
+                @Nullable byte[] data,
+                @Nullable HotwordDetectedResult hotwordDetectedResult,
+                @Nullable ParcelFileDescriptor audioStream,
+                @NonNull List<KeyphraseRecognitionExtra> keyphraseExtras) {
             mCaptureAvailable = captureAvailable;
             mCaptureSession = captureSession;
             mAudioFormat = audioFormat;
+            mDataFormat = dataFormat;
             mData = data;
             mHotwordDetectedResult = hotwordDetectedResult;
             mAudioStream = audioStream;
+            mKephraseExtras = keyphraseExtras;
         }
 
         /**
@@ -400,10 +420,12 @@
          * {@link #getCaptureAudioFormat()}.
          *
          * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+         * @deprecated Use {@link #getData()} instead.
          */
+        @Deprecated
         @Nullable
         public byte[] getTriggerAudio() {
-            if (mTriggerAvailable) {
+            if (mDataFormat == DATA_FORMAT_TRIGGER_AUDIO) {
                 return mData;
             } else {
                 return null;
@@ -411,6 +433,36 @@
         }
 
         /**
+         * Conveys the format of the additional data that is triggered with the keyphrase event.
+         *
+         * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+         * @see DataFormat
+         */
+        @DataFormat
+        public int getDataFormat() {
+            return mDataFormat;
+        }
+
+        /**
+         * Gets additional raw data that is triggered with the keyphrase event.
+         *
+         * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this
+         * field with opaque data for use by system applications who know about voice
+         * engine internals. Data may be null if the field is not populated by the
+         * {@link android.hardware.soundtrigger.SoundTriggerModule}.
+         *
+         * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the
+         * entirety of this buffer is expected to be of the format from
+         * {@link #getCaptureAudioFormat()}.
+         *
+         * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+         */
+        @Nullable
+        public byte[] getData() {
+            return mData;
+        }
+
+        /**
          * Gets the session ID to start a capture from the DSP.
          * This may be null if streaming capture isn't possible.
          * If non-null, the format of the audio that can be captured can be
@@ -464,6 +516,166 @@
         public ParcelFileDescriptor getAudioStream() {
             return mAudioStream;
         }
+
+        /**
+         * Returns the keyphrases recognized by the voice engine with additional confidence
+         * information
+         *
+         * @return List of keyphrase extras describing additional data for each keyphrase the voice
+         * engine triggered on for this event. The ordering of the list is preserved based on what
+         * the ordering provided by {@link android.hardware.soundtrigger.SoundTriggerModule}.
+         */
+        @NonNull
+        public List<KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras() {
+            return mKephraseExtras;
+        }
+
+        /**
+         * Builder class for {@link EventPayload} objects
+         *
+         * @hide
+         */
+        @TestApi
+        public static final class Builder {
+            private boolean mCaptureAvailable = false;
+            private int mCaptureSession = -1;
+            private AudioFormat mAudioFormat = null;
+            @DataFormat
+            private int mDataFormat = DATA_FORMAT_RAW;
+            private byte[] mData = null;
+            private HotwordDetectedResult mHotwordDetectedResult = null;
+            private ParcelFileDescriptor mAudioStream = null;
+            private List<KeyphraseRecognitionExtra> mKeyphraseExtras = Collections.emptyList();
+
+            public Builder() {}
+
+            Builder(SoundTrigger.KeyphraseRecognitionEvent keyphraseRecognitionEvent) {
+                setCaptureAvailable(keyphraseRecognitionEvent.isCaptureAvailable());
+                setCaptureSession(keyphraseRecognitionEvent.getCaptureSession());
+                if (keyphraseRecognitionEvent.getCaptureFormat() != null) {
+                    setCaptureAudioFormat(keyphraseRecognitionEvent.getCaptureFormat());
+                }
+                setDataFormat((keyphraseRecognitionEvent.triggerInData) ? DATA_FORMAT_TRIGGER_AUDIO
+                        : DATA_FORMAT_RAW);
+                if (keyphraseRecognitionEvent.getData() != null) {
+                    setData(keyphraseRecognitionEvent.getData());
+                }
+                if (keyphraseRecognitionEvent.keyphraseExtras != null) {
+                    setKeyphraseRecognitionExtras(
+                            Arrays.asList(keyphraseRecognitionEvent.keyphraseExtras));
+                }
+            }
+
+            /**
+             * Indicates if {@code captureSession} can be used to continue capturing more audio from
+             * the DSP hardware.
+             */
+            @SuppressLint("MissingGetterMatchingBuilder")
+            @NonNull
+            public Builder setCaptureAvailable(boolean captureAvailable) {
+                mCaptureAvailable = captureAvailable;
+                return this;
+            }
+
+            /**
+             * Sets the session ID to start a capture from the DSP.
+             */
+            @SuppressLint("MissingGetterMatchingBuilder")
+            @NonNull
+            public Builder setCaptureSession(int captureSession) {
+                mCaptureSession = captureSession;
+                return this;
+            }
+
+            /**
+             * Sets the format of the audio obtained using {@link #getTriggerAudio()}.
+             */
+            @NonNull
+            public Builder setCaptureAudioFormat(@NonNull AudioFormat audioFormat) {
+                mAudioFormat = audioFormat;
+                return this;
+            }
+
+            /**
+             * Conveys the format of the additional data that is triggered with the keyphrase event.
+             *
+             * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+             * @see DataFormat
+             */
+            @NonNull
+            public Builder setDataFormat(@DataFormat int dataFormat) {
+                mDataFormat = dataFormat;
+                return this;
+            }
+
+            /**
+             * Sets additional raw data that is triggered with the keyphrase event.
+             *
+             * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this
+             * field with opaque data for use by system applications who know about voice
+             * engine internals. Data may be null if the field is not populated by the
+             * {@link android.hardware.soundtrigger.SoundTriggerModule}.
+             *
+             * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the
+             * entirety of this
+             * buffer is expected to be of the format from {@link #getCaptureAudioFormat()}.
+             *
+             * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+             */
+            @NonNull
+            public Builder setData(@NonNull byte[] data) {
+                mData = data;
+                return this;
+            }
+
+            /**
+             * Sets {@link HotwordDetectedResult} associated with the hotword event, passed from
+             * {@link HotwordDetectionService}.
+             */
+            @NonNull
+            public Builder setHotwordDetectedResult(
+                    @NonNull HotwordDetectedResult hotwordDetectedResult) {
+                mHotwordDetectedResult = hotwordDetectedResult;
+                return this;
+            }
+
+            /**
+             * Sets a stream with bytes corresponding to the open audio stream with hotword data.
+             *
+             * <p>This data represents an audio stream in the format returned by
+             * {@link #getCaptureAudioFormat}.
+             *
+             * <p>Clients are expected to start consuming the stream within 1 second of receiving
+             * the
+             * event.
+             */
+            @NonNull
+            public Builder setAudioStream(@NonNull ParcelFileDescriptor audioStream) {
+                mAudioStream = audioStream;
+                return this;
+            }
+
+            /**
+             * Sets the keyphrases recognized by the voice engine with additional confidence
+             * information
+             */
+            @NonNull
+            public Builder setKeyphraseRecognitionExtras(
+                    @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras) {
+                mKeyphraseExtras = keyphraseRecognitionExtras;
+                return this;
+            }
+
+            /**
+             * Builds an {@link EventPayload} instance
+             */
+            @NonNull
+            public EventPayload build() {
+                return new EventPayload(mCaptureAvailable, mAudioFormat, mCaptureSession,
+                        mDataFormat, mData, mHotwordDetectedResult, mAudioStream,
+                        mKeyphraseExtras);
+            }
+        }
     }
 
     /**
@@ -993,11 +1205,14 @@
     /**
      * Invalidates this hotword detector so that any future calls to this result
      * in an IllegalStateException.
-     *
-     * @hide
      */
-    void invalidate() {
+    @Override
+    public void destroy() {
         synchronized (mLock) {
+            if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
+                stopRecognition();
+            }
+
             mAvailability = STATE_INVALID;
             notifyStateChangedLocked();
 
@@ -1009,6 +1224,7 @@
                 }
             }
         }
+        super.destroy();
     }
 
     /**
@@ -1171,8 +1387,9 @@
                 Slog.i(TAG, "onDetected");
             }
             Message.obtain(mHandler, MSG_HOTWORD_DETECTED,
-                    new EventPayload(event.triggerInData, event.captureAvailable,
-                            event.captureFormat, event.captureSession, event.data, result))
+                    new EventPayload.Builder(event)
+                            .setHotwordDetectedResult(result)
+                            .build())
                     .sendToTarget();
         }
         @Override
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index e3bb589..dfe0f54 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -140,9 +140,7 @@
                 Log.d(TAG, "#detectFromDspSource");
             }
             HotwordDetectionService.this.onDetect(
-                    new AlwaysOnHotwordDetector.EventPayload(
-                            event.triggerInData, event.captureAvailable,
-                            event.captureFormat, event.captureSession, event.data),
+                    new AlwaysOnHotwordDetector.EventPayload.Builder(event).build(),
                     timeoutMillis,
                     new Callback(callback));
         }
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 969ec22..96fd8bb 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -119,6 +119,17 @@
     void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory);
 
     /**
+     * Invalidates this hotword detector so that any future calls to this result
+     * in an {@link IllegalStateException}.
+     *
+     * <p>If there are no other {@link HotwordDetector} instances linked to the
+     * {@link HotwordDetectionService}, the service will be shutdown.
+     */
+    default void destroy() {
+        throw new UnsupportedOperationException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * @hide
      */
     static String detectorTypeToString(int detectorType) {
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 512a654..2d662ea 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -77,7 +77,7 @@
         if (DEBUG) {
             Slog.i(TAG, "#startRecognition");
         }
-
+        throwIfDetectorIsNoLongerActive();
         maybeCloseExistingSession();
 
         try {
@@ -100,6 +100,7 @@
         if (DEBUG) {
             Slog.i(TAG, "#stopRecognition");
         }
+        throwIfDetectorIsNoLongerActive();
 
         try {
             mManagerService.stopListeningFromMic();
@@ -110,6 +111,19 @@
         return true;
     }
 
+    @Override
+    public void destroy() {
+        stopRecognition();
+        maybeCloseExistingSession();
+
+        try {
+            mManagerService.shutdownHotwordDetectionService();
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+        super.destroy();
+    }
+
     private void maybeCloseExistingSession() {
         // TODO: needs to be synchronized.
         // TODO: implement this
@@ -135,8 +149,11 @@
             mHandler.sendMessage(obtainMessage(
                     HotwordDetector.Callback::onDetected,
                     mCallback,
-                    new AlwaysOnHotwordDetector.EventPayload(
-                            audioFormat, hotwordDetectedResult, audioStream)));
+                    new AlwaysOnHotwordDetector.EventPayload.Builder()
+                            .setCaptureAudioFormat(audioFormat)
+                            .setAudioStream(audioStream)
+                            .setHotwordDetectedResult(hotwordDetectedResult)
+                            .build()));
         }
     }
 
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index f52c9ff..bf0cfbe 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -271,7 +271,7 @@
         // It's still guaranteed to have been stopped.
         // This helps with cases where the voice interaction implementation is changed
         // by the user.
-        safelyShutdownHotwordDetector();
+        safelyShutdownAllHotwordDetectors();
     }
 
     /**
@@ -380,11 +380,13 @@
         }
         synchronized (mLock) {
             // Allow only one concurrent recognition via the APIs.
-            safelyShutdownHotwordDetector();
+            safelyShutdownAllHotwordDetectors();
             mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
                     mKeyphraseEnrollmentInfo, mSystemService,
                     getApplicationContext().getApplicationInfo().targetSdkVersion,
                     supportHotwordDetectionService, options, sharedMemory);
+            mHotwordDetector.registerOnDestroyListener((detector) -> onDspHotwordDetectorDestroyed(
+                    (AlwaysOnHotwordDetector) detector));
         }
         return mHotwordDetector;
     }
@@ -433,10 +435,13 @@
         }
         synchronized (mLock) {
             // Allow only one concurrent recognition via the APIs.
-            safelyShutdownHotwordDetector();
+            safelyShutdownAllHotwordDetectors();
             mSoftwareHotwordDetector =
                     new SoftwareHotwordDetector(
                             mSystemService, null, options, sharedMemory, callback);
+            mSoftwareHotwordDetector.registerOnDestroyListener(
+                    (detector) -> onMicrophoneHotwordDetectorDestroyed(
+                            (SoftwareHotwordDetector) detector));
         }
         return mSoftwareHotwordDetector;
     }
@@ -482,51 +487,36 @@
         return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
     }
 
-    private void safelyShutdownHotwordDetector() {
+    private void safelyShutdownAllHotwordDetectors() {
         synchronized (mLock) {
-            shutdownDspHotwordDetectorLocked();
-            shutdownMicrophoneHotwordDetectorLocked();
+            if (mHotwordDetector != null) {
+                try {
+                    mHotwordDetector.destroy();
+                } catch (Exception ex) {
+                    Log.i(TAG, "exception destroying AlwaysOnHotwordDetector", ex);
+                }
+            }
+
+            if (mSoftwareHotwordDetector != null) {
+                try {
+                    mSoftwareHotwordDetector.destroy();
+                } catch (Exception ex) {
+                    Log.i(TAG, "exception destroying SoftwareHotwordDetector", ex);
+                }
+            }
         }
     }
 
-    private void shutdownDspHotwordDetectorLocked() {
-        if (mHotwordDetector == null) {
-            return;
+    private void onDspHotwordDetectorDestroyed(@NonNull AlwaysOnHotwordDetector detector) {
+        synchronized (mLock) {
+            mHotwordDetector = null;
         }
-
-        try {
-            mHotwordDetector.stopRecognition();
-        } catch (Exception ex) {
-            // Ignore.
-        }
-
-        try {
-            mHotwordDetector.invalidate();
-        } catch (Exception ex) {
-            // Ignore.
-        }
-
-        mHotwordDetector = null;
     }
 
-    private void shutdownMicrophoneHotwordDetectorLocked() {
-        if (mSoftwareHotwordDetector == null) {
-            return;
+    private void onMicrophoneHotwordDetectorDestroyed(@NonNull SoftwareHotwordDetector detector) {
+        synchronized (mLock) {
+            mSoftwareHotwordDetector = null;
         }
-
-        try {
-            mSoftwareHotwordDetector.stopRecognition();
-        } catch (Exception ex) {
-            // Ignore.
-        }
-
-        try {
-            mSystemService.shutdownHotwordDetectionService();
-        } catch (Exception ex) {
-            // Ignore.
-        }
-
-        mSoftwareHotwordDetector = null;
     }
 
     /**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c91851a..a2938a8 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -26,6 +26,8 @@
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
@@ -890,8 +892,6 @@
          * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper.
          */
         private void updateWallpaperDimming(float dimAmount) {
-            mPreviousWallpaperDimAmount = mWallpaperDimAmount;
-
             // Custom dim amount cannot be less than the default dim amount.
             mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount);
             // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim
@@ -909,15 +909,15 @@
             SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
             // TODO: apply the dimming to preview as well once surface transparency works in
             // preview mode.
-            if (!isPreview() && mShouldDim) {
+            if ((!isPreview() && mShouldDim)
+                    || mPreviousWallpaperDimAmount != mWallpaperDimAmount) {
                 Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
 
                 // Animate dimming to gradually change the wallpaper alpha from the previous
                 // dim amount to the new amount only if the dim amount changed.
                 ValueAnimator animator = ValueAnimator.ofFloat(
                         mPreviousWallpaperDimAmount, mWallpaperDimAmount);
-                animator.setDuration(mPreviousWallpaperDimAmount == mWallpaperDimAmount
-                        ? 0 : DIMMING_ANIMATION_DURATION_MS);
+                animator.setDuration(DIMMING_ANIMATION_DURATION_MS);
                 animator.addUpdateListener((ValueAnimator va) -> {
                     final float dimValue = (float) va.getAnimatedValue();
                     if (mBbqSurfaceControl != null) {
@@ -925,11 +925,19 @@
                                 .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
                     }
                 });
+                animator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        updateSurface(false, false, true);
+                    }
+                });
                 animator.start();
             } else {
                 Log.v(TAG, "Setting wallpaper dimming: " + 0);
                 surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
             }
+
+            mPreviousWallpaperDimAmount = mWallpaperDimAmount;
         }
 
         /**
@@ -1332,6 +1340,7 @@
                             resetWindowPages();
                             mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
                             processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+                            notifyColorsChanged();
                         }
                         reposition();
                         reportEngineShown(shouldWaitForEngineShown());
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 502558e..e075c05 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -496,6 +496,17 @@
         Objects.requireNonNull(recognizerIntent, "intent must not be null");
         Objects.requireNonNull(supportListener, "listener must not be null");
 
+        if (DBG) {
+            Slog.i(TAG, "#checkRecognitionSupport called");
+            if (mService == null) {
+                Slog.i(TAG, "Connection is not established yet");
+            }
+        }
+
+        if (mService == null) {
+            // First time connection: first establish a connection, then dispatch.
+            connectToSystemService();
+        }
         putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT,
                 Pair.create(recognizerIntent, supportListener)));
     }
@@ -510,7 +521,17 @@
      */
     public void triggerModelDownload(@NonNull Intent recognizerIntent) {
         Objects.requireNonNull(recognizerIntent, "intent must not be null");
-        putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD));
+        if (DBG) {
+            Slog.i(TAG, "#triggerModelDownload called");
+            if (mService == null) {
+                Slog.i(TAG, "Connection is not established yet");
+            }
+        }
+        if (mService == null) {
+            // First time connection: first establish a connection, then dispatch.
+            connectToSystemService();
+        }
+        putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
     }
 
     /**
@@ -625,7 +646,6 @@
         }
         try {
             mService.triggerModelDownload(recognizerIntent);
-            if (DBG) Log.d(TAG, "service download support command succeeded");
         } catch (final RemoteException e) {
             Log.e(TAG, "downloadModel() failed", e);
             mListener.onError(ERROR_CLIENT);
@@ -705,6 +725,9 @@
     }
 
     private synchronized boolean maybeInitializeManagerService() {
+        if (DBG) {
+            Log.i(TAG, "#maybeInitializeManagerService found = " + mManagerService);
+        }
         if (mManagerService != null) {
             return true;
         }
@@ -712,8 +735,13 @@
         mManagerService = IRecognitionServiceManager.Stub.asInterface(
                 ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
 
-        if (mManagerService == null && mListener != null) {
-            mListener.onError(ERROR_CLIENT);
+        if (DBG) {
+            Log.i(TAG, "#maybeInitializeManagerService instantiated =" + mManagerService);
+        }
+        if (mManagerService == null) {
+            if (mListener != null) {
+                mListener.onError(ERROR_CLIENT);
+            }
             return false;
         }
         return true;
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
index 422d347..dfa172d 100644
--- a/core/java/android/text/BidiFormatter.java
+++ b/core/java/android/text/BidiFormatter.java
@@ -31,7 +31,7 @@
  * directionality of the text can be either estimated or passed in when known.
  *
  * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
- * you can use the support library's {@link android.support.v4.text.BidiFormatter} class.
+ * you can use the support library's {@link androidx.core.text.BidiFormatter} class.
  *
  * <p>These APIs provides the following functionality:
  * <p>
diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java
index 354c15f..85260f4 100644
--- a/core/java/android/text/TextDirectionHeuristics.java
+++ b/core/java/android/text/TextDirectionHeuristics.java
@@ -28,7 +28,7 @@
  * provided in the {@link android.view.View} class for {@link android.view.View#setTextDirection
  * setTextDirection()}, such as {@link android.view.View#TEXT_DIRECTION_RTL}.
  * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
- * you can use the support library's {@link android.support.v4.text.TextDirectionHeuristicsCompat}
+ * you can use the support library's {@link androidx.core.text.TextDirectionHeuristicsCompat}
  * class.
  *
  */
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 4a48832..ad044af 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -217,6 +217,7 @@
                 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
         mMisspelledUnderlineColor = typedArray.getColor(
                 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+        typedArray.recycle();
 
         defStyleAttr = com.android.internal.R.attr.textAppearanceGrammarErrorSuggestion;
         typedArray = context.obtainStyledAttributes(
@@ -225,6 +226,7 @@
                 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
         mGrammarErrorUnderlineColor = typedArray.getColor(
                 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+        typedArray.recycle();
 
         defStyleAttr = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
         typedArray = context.obtainStyledAttributes(
@@ -233,6 +235,7 @@
                 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
         mEasyCorrectUnderlineColor = typedArray.getColor(
                 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+        typedArray.recycle();
 
         defStyleAttr = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion;
         typedArray = context.obtainStyledAttributes(
@@ -241,6 +244,7 @@
                 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
         mAutoCorrectionUnderlineColor = typedArray.getColor(
                 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+        typedArray.recycle();
     }
 
     public SuggestionSpan(Parcel src) {
diff --git a/core/java/android/tracing/OWNERS b/core/java/android/tracing/OWNERS
index f5de4eb..7d1b48b 100644
--- a/core/java/android/tracing/OWNERS
+++ b/core/java/android/tracing/OWNERS
@@ -1,2 +1,2 @@
[email protected]
 [email protected]
+include platform/external/perfetto:/OWNERS
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index 0ac2c9c..cebdbf6 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -24,8 +24,10 @@
 import android.annotation.Dimension;
 import android.graphics.Insets;
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.Surface.Rotation;
+import android.view.SurfaceControl;
 
 /**
  * A class containing utility methods related to rotation.
@@ -121,13 +123,64 @@
 
     /** @return the rotation needed to rotate from oldRotation to newRotation. */
     @Rotation
-    public static int deltaRotation(int oldRotation, int newRotation) {
+    public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) {
         int delta = newRotation - oldRotation;
         if (delta < 0) delta += 4;
         return delta;
     }
 
     /**
+     * Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the
+     * bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left
+     * corner appropriately.
+     */
+    public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc,
+            @Rotation int rotation) {
+        // Note: the matrix values look inverted, but they aren't because our coordinate-space
+        // is actually left-handed.
+        // Note: setMatrix expects values in column-major order.
+        switch (rotation) {
+            case ROTATION_0:
+                t.setMatrix(sc, 1.f, 0.f, 0.f, 1.f);
+                break;
+            case ROTATION_90:
+                t.setMatrix(sc, 0.f, -1.f, 1.f, 0.f);
+                break;
+            case ROTATION_180:
+                t.setMatrix(sc, -1.f, 0.f, 0.f, -1.f);
+                break;
+            case ROTATION_270:
+                t.setMatrix(sc, 0.f, 1.f, -1.f, 0.f);
+                break;
+        }
+    }
+
+    /**
+     * Rotates a point CCW within a rectangle of size parentW x parentH with top/left at the
+     * origin as if the point is stuck to the rectangle. The rectangle is transformed such that
+     * it's top/left remains at the origin after the rotation.
+     */
+    public static void rotatePoint(Point inOutPoint, @Rotation int rotation,
+            int parentW, int parentH) {
+        int origX = inOutPoint.x;
+        switch (rotation) {
+            case ROTATION_0:
+                return;
+            case ROTATION_90:
+                inOutPoint.x = inOutPoint.y;
+                inOutPoint.y = parentW - origX;
+                return;
+            case ROTATION_180:
+                inOutPoint.x = parentW - inOutPoint.x;
+                inOutPoint.y = parentH - inOutPoint.y;
+                return;
+            case ROTATION_270:
+                inOutPoint.x = parentH - inOutPoint.y;
+                inOutPoint.y = origX;
+        }
+    }
+
+    /**
      * Sets a matrix such that given a rotation, it transforms physical display
      * coordinates to that rotation's logical coordinates.
      *
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index 117d75e..3aeecca 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -16,6 +16,9 @@
 
 package android.util;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
@@ -24,118 +27,265 @@
  *
  * <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs.
  *
+ * @see Log
  * @hide
  */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class Slog {
 
     private Slog() {
     }
 
+    /**
+     * Logs {@code msg} at {@link Log#VERBOSE} level.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     *
+     * @see Log#v(String, String)
+     */
     @UnsupportedAppUsage
-    public static int v(String tag, String msg) {
+    public static int v(@Nullable String tag, @NonNull String msg) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg);
     }
 
-    public static int v(String tag, String msg, Throwable tr) {
+    /**
+     * Logs {@code msg} at {@link Log#VERBOSE} level, attaching stack trace of the {@code tr} to
+     * the end of the log statement.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     * @param tr an exception to log.
+     *
+     * @see Log#v(String, String, Throwable)
+     */
+    public static int v(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag,
                 msg + '\n' + Log.getStackTraceString(tr));
     }
 
+    /**
+     * Logs {@code msg} at {@link Log#DEBUG} level.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     *
+     * @see Log#d(String, String)
+     */
     @UnsupportedAppUsage
-    public static int d(String tag, String msg) {
+    public static int d(@Nullable String tag, @NonNull String msg) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
     }
 
+    /**
+     * Logs {@code msg} at {@link Log#DEBUG} level, attaching stack trace of the {@code tr} to
+     * the end of the log statement.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     * @param tr an exception to log.
+     *
+     * @see Log#d(String, String, Throwable)
+     */
     @UnsupportedAppUsage
-    public static int d(String tag, String msg, Throwable tr) {
+    public static int d(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag,
                 msg + '\n' + Log.getStackTraceString(tr));
     }
 
+    /**
+     * Logs {@code msg} at {@link Log#INFO} level.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     *
+     * @see Log#i(String, String)
+     */
     @UnsupportedAppUsage
-    public static int i(String tag, String msg) {
+    public static int i(@Nullable String tag, @NonNull String msg) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg);
     }
 
-    public static int i(String tag, String msg, Throwable tr) {
+    /**
+     * Logs {@code msg} at {@link Log#INFO} level, attaching stack trace of the {@code tr} to
+     * the end of the log statement.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     * @param tr an exception to log.
+     *
+     * @see Log#i(String, String, Throwable)
+     */
+    public static int i(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag,
                 msg + '\n' + Log.getStackTraceString(tr));
     }
 
+    /**
+     * Logs {@code msg} at {@link Log#WARN} level.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     *
+     * @see Log#w(String, String)
+     */
     @UnsupportedAppUsage
-    public static int w(String tag, String msg) {
+    public static int w(@Nullable String tag, @NonNull String msg) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg);
     }
 
+    /**
+     * Logs {@code msg} at {@link Log#WARN} level, attaching stack trace of the {@code tr} to
+     * the end of the log statement.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     * @param tr an exception to log.
+     *
+     * @see Log#w(String, String, Throwable)
+     */
     @UnsupportedAppUsage
-    public static int w(String tag, String msg, Throwable tr) {
+    public static int w(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag,
                 msg + '\n' + Log.getStackTraceString(tr));
     }
 
-    public static int w(String tag, Throwable tr) {
+    /**
+     * Logs stack trace of {@code tr} at {@link Log#WARN} level.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param tr an exception to log.
+     *
+     * @see Log#w(String, Throwable)
+     */
+    public static int w(@Nullable String tag, @Nullable Throwable tr) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, Log.getStackTraceString(tr));
     }
 
+    /**
+     * Logs {@code msg} at {@link Log#ERROR} level.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     *
+     * @see Log#e(String, String)
+     */
     @UnsupportedAppUsage
-    public static int e(String tag, String msg) {
+    public static int e(@Nullable String tag, @NonNull String msg) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
     }
 
+    /**
+     * Logs {@code msg} at {@link Log#ERROR} level, attaching stack trace of the {@code tr} to
+     * the end of the log statement.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     * @param tr an exception to log.
+     *
+     * @see Log#e(String, String, Throwable)
+     */
     @UnsupportedAppUsage
-    public static int e(String tag, String msg, Throwable tr) {
+    public static int e(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
         return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag,
                 msg + '\n' + Log.getStackTraceString(tr));
     }
 
     /**
-     * Like {@link Log#wtf(String, String)}, but will never cause the caller to crash, and
-     * will always be handled asynchronously.  Primarily for use by coding running within
-     * the system process.
+     * Logs a condition that should never happen.
+     *
+     * <p>
+     * Similar to {@link Log#wtf(String, String)}, but will never cause the caller to crash, and
+     * will always be handled asynchronously. Primarily to be used by the system server.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     *
+     * @see Log#wtf(String, String)
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static int wtf(String tag, String msg) {
+    public static int wtf(@Nullable String tag, @NonNull String msg) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true);
     }
 
     /**
-     * Like {@link #wtf(String, String)}, but does not output anything to the log.
+     * Similar to {@link #wtf(String, String)}, but does not output anything to the log.
      */
-    public static void wtfQuiet(String tag, String msg) {
+    public static void wtfQuiet(@Nullable String tag, @NonNull String msg) {
         Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true);
     }
 
     /**
-     * Like {@link Log#wtfStack(String, String)}, but will never cause the caller to crash, and
-     * will always be handled asynchronously.  Primarily for use by coding running within
-     * the system process.
+     * Logs a condition that should never happen, attaching the full call stack to the log.
+     *
+     * <p>
+     * Similar to {@link Log#wtfStack(String, String)}, but will never cause the caller to crash,
+     * and will always be handled asynchronously. Primarily to be used by the system server.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     *
+     * @see Log#wtfStack(String, String)
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public static int wtfStack(String tag, String msg) {
+    public static int wtfStack(@Nullable String tag, @NonNull String msg) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true);
     }
 
     /**
-     * Like {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash,
-     * and will always be handled asynchronously.  Primarily for use by coding running within
-     * the system process.
+     * Logs a condition that should never happen, attaching stack trace of the {@code tr} to the
+     * end of the log statement.
+     *
+     * <p>
+     * Similar to {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash,
+     * and will always be handled asynchronously. Primarily to be used by the system server.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param tr an exception to log.
+     *
+     * @see Log#wtf(String, Throwable)
      */
-    public static int wtf(String tag, Throwable tr) {
+    public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true);
     }
 
     /**
-     * Like {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to crash,
-     * and will always be handled asynchronously.  Primarily for use by coding running within
-     * the system process.
+     * Logs a condition that should never happen, attaching stack trace of the {@code tr} to the
+     * end of the log statement.
+     *
+     * <p>
+     * Similar to {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to
+     * crash, and will always be handled asynchronously. Primarily to be used by the system server.
+     *
+     * @param tag identifies the source of a log message.  It usually represents system service,
+     *            e.g. {@code PackageManager}.
+     * @param msg the message to log.
+     * @param tr an exception to log.
+     *
+     * @see Log#wtf(String, String, Throwable)
      */
     @UnsupportedAppUsage
-    public static int wtf(String tag, String msg, Throwable tr) {
+    public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true);
     }
 
+    /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static int println(int priority, String tag, String msg) {
+    public static int println(@Log.Level int priority, @Nullable String tag, @NonNull String msg) {
         return Log.println_native(Log.LOG_ID_SYSTEM, priority, tag, msg);
     }
 }
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index de56d3a..43c07c8 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -52,9 +52,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -348,6 +349,11 @@
 
         View requestedView = null;
         AccessibilityNodeInfo requestedNode = null;
+        boolean interruptPrefetch =
+                ((flags & AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE) == 0);
+
+        ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+        infos.clear();
         try {
             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
                 return;
@@ -357,27 +363,46 @@
             if (requestedView != null && isShown(requestedView)) {
                 requestedNode = populateAccessibilityNodeInfoForView(
                         requestedView, arguments, virtualDescendantId);
+                mPrefetcher.mInterruptPrefetch = interruptPrefetch;
+                mPrefetcher.mFetchFlags = flags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+
+                if (!interruptPrefetch) {
+                    infos.add(requestedNode);
+                    mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
+                            requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
+                            infos);
+                    mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+                }
             }
         } finally {
-            updateInfoForViewportAndReturnFindNodeResult(
-                    requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode),
-                    callback, interactionId, spec, interactiveRegion);
+            if (!interruptPrefetch) {
+                // Return found node and prefetched nodes in one IPC.
+                updateInfosForViewportAndReturnFindNodeResult(infos, callback, interactionId, spec,
+                        interactiveRegion);
+
+                final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
+                        getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode,
+                               infos, flags);
+                if (satisfiedRequest != null) {
+                    returnFindNodeResult(satisfiedRequest);
+                }
+                return;
+            } else {
+                // Return found node.
+                updateInfoForViewportAndReturnFindNodeResult(
+                        requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
+                        callback, interactionId, spec, interactiveRegion);
+            }
         }
-        ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
-        infos.clear();
         mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
-                requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode),
-                virtualDescendantId, flags, infos);
+                requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos);
         mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
         updateInfosForViewPort(infos, spec, interactiveRegion);
         final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
-                getSatisfiedRequestInPrefetch(
-                        requestedNode == null ? null : requestedNode, infos, flags);
+                getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos,
+                        flags);
 
-        if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode != requestedNode) {
-            infos.remove(satisfiedRequest.mSatisfiedRequestNode);
-        }
-
+        // Return prefetch result separately.
         returnPrefetchResult(interactionId, infos, callback);
 
         if (satisfiedRequest != null) {
@@ -1077,6 +1102,11 @@
                 }
             }
             mPendingFindNodeByIdMessages.clear();
+            // Remove node from prefetched infos.
+            if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode
+                    != requestedNode) {
+                infos.remove(satisfiedRequest.mSatisfiedRequestNode);
+            }
             return satisfiedRequest;
         }
     }
@@ -1149,45 +1179,76 @@
      */
     private class AccessibilityNodePrefetcher {
 
-        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
-
         private final ArrayList<View> mTempViewList = new ArrayList<View>();
+        private boolean mInterruptPrefetch;
+        private int mFetchFlags;
 
         public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root,
-                int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) {
+                List<AccessibilityNodeInfo> outInfos) {
             if (root == null) {
                 return;
             }
             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+            final boolean prefetchPredecessors =
+                    isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS);
             if (provider == null) {
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+                if (prefetchPredecessors) {
                     prefetchPredecessorsOfRealNode(view, outInfos);
                 }
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
-                    prefetchSiblingsOfRealNode(view, outInfos);
+                if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) {
+                    prefetchSiblingsOfRealNode(view, outInfos, prefetchPredecessors);
                 }
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+                if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) {
                     prefetchDescendantsOfRealNode(view, outInfos);
                 }
             } else {
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+                if (prefetchPredecessors) {
                     prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
                 }
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
-                    prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+                if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) {
+                    prefetchSiblingsOfVirtualNode(root, view, provider, outInfos,
+                            prefetchPredecessors);
                 }
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+                if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) {
                     prefetchDescendantsOfVirtualNode(root, provider, outInfos);
                 }
             }
+            if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST)
+                    || isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST)) {
+                if (shouldStopPrefetching(outInfos)) {
+                    return;
+                }
+                PrefetchDeque<DequeNode> deque = new PrefetchDeque<>(
+                        mFetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK,
+                        outInfos);
+                addChildrenOfRoot(view, root, provider, deque);
+                deque.performTraversalAndPrefetch();
+            }
             if (ENFORCE_NODE_TREE_CONSISTENT) {
                 enforceNodeTreeConsistent(root, outInfos);
             }
         }
 
-        private boolean shouldStopPrefetching(List prefetchededInfos) {
-            return mHandler.hasUserInteractiveMessagesWaiting()
-                    || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE;
+        private void addChildrenOfRoot(View root, AccessibilityNodeInfo rootInfo,
+                AccessibilityNodeProvider rootProvider, PrefetchDeque deque) {
+            DequeNode rootDequeNode;
+            if (rootProvider == null) {
+                rootDequeNode = new ViewNode(root);
+            } else {
+                rootDequeNode = new VirtualNode(
+                        AccessibilityNodeProvider.HOST_VIEW_ID, rootProvider);
+            }
+            rootDequeNode.addChildren(rootInfo, deque);
+        }
+
+        private boolean isFlagSet(@AccessibilityNodeInfo.PrefetchingStrategy int strategy) {
+            return (mFetchFlags & strategy) != 0;
+        }
+
+        public boolean shouldStopPrefetching(List prefetchedInfos) {
+            return ((mHandler.hasUserInteractiveMessagesWaiting() && mInterruptPrefetch)
+                    || prefetchedInfos.size()
+                    >= AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES);
         }
 
         private void enforceNodeTreeConsistent(
@@ -1283,7 +1344,7 @@
         }
 
         private void prefetchSiblingsOfRealNode(View current,
-                List<AccessibilityNodeInfo> outInfos) {
+                List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched) {
             if (shouldStopPrefetching(outInfos)) {
                 return;
             }
@@ -1293,6 +1354,13 @@
                 ArrayList<View> children = mTempViewList;
                 children.clear();
                 try {
+                    if (!predecessorsPrefetched) {
+                        AccessibilityNodeInfo parentInfo =
+                                ((ViewGroup) parent).createAccessibilityNodeInfo();
+                        if (parentInfo != null) {
+                            outInfos.add(parentInfo);
+                        }
+                    }
                     parentGroup.addChildrenForAccessibility(children);
                     final int childCount = children.size();
                     for (int i = 0; i < childCount; i++) {
@@ -1304,7 +1372,7 @@
                                 && isShown(child)) {
                             AccessibilityNodeInfo info = null;
                             AccessibilityNodeProvider provider =
-                                child.getAccessibilityNodeProvider();
+                                    child.getAccessibilityNodeProvider();
                             if (provider == null) {
                                 info = child.createAccessibilityNodeInfo();
                             } else {
@@ -1327,8 +1395,8 @@
             if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
                 return;
             }
-            HashMap<View, AccessibilityNodeInfo> addedChildren =
-                new HashMap<View, AccessibilityNodeInfo>();
+            LinkedHashMap<View, AccessibilityNodeInfo> addedChildren =
+                    new LinkedHashMap<View, AccessibilityNodeInfo>();
             ArrayList<View> children = mTempViewList;
             children.clear();
             try {
@@ -1414,17 +1482,21 @@
         }
 
         private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
-                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos,
+                boolean predecessorsPrefetched) {
             final long parentNodeId = current.getParentNodeId();
             final int parentAccessibilityViewId =
-                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+                    AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
             final int parentVirtualDescendantId =
-                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
             if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
                     || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
                 final AccessibilityNodeInfo parent =
                         provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
                 if (parent != null) {
+                    if (!predecessorsPrefetched) {
+                        outInfos.add(parent);
+                    }
                     final int childCount = parent.getChildCount();
                     for (int i = 0; i < childCount; i++) {
                         if (shouldStopPrefetching(outInfos)) {
@@ -1443,7 +1515,7 @@
                     }
                 }
             } else {
-                prefetchSiblingsOfRealNode(providerHost, outInfos);
+                prefetchSiblingsOfRealNode(providerHost, outInfos, predecessorsPrefetched);
             }
         }
 
@@ -1626,4 +1698,159 @@
             mSatisfiedRequestInteractionId = satisfiedRequestInteractionId;
         }
     }
+
+    private class PrefetchDeque<E extends DequeNode>
+            extends ArrayDeque<E> {
+        int mStrategy;
+        List<AccessibilityNodeInfo> mPrefetchOutput;
+
+        PrefetchDeque(int strategy, List<AccessibilityNodeInfo> output) {
+            mStrategy = strategy;
+            mPrefetchOutput = output;
+        }
+
+        /** Performs depth-first or breadth-first traversal.
+         *
+         * For depth-first search, we iterate through the children in backwards order and push them
+         * to the stack before taking from the head. For breadth-first search, we iterate through
+         * the children in order and push them to the stack before taking from the tail.
+         *
+         * Depth-first search:  0 has children 0, 1, 2, 4. 1 has children 5 and 6.
+         * Head         Tail
+         * 1  2  3  4 ->  pop: 1 -> 5  6  2  3  4
+         *
+         * Breadth-first search
+         * Head         Tail
+         * 4  3  2  1 -> remove last: 1 -> 6  5  3  2
+         *
+         **/
+        void performTraversalAndPrefetch() {
+            try {
+                while (!isEmpty()) {
+                    E child = getNext();
+                    AccessibilityNodeInfo childInfo = child.getA11yNodeInfo();
+                    if (childInfo != null) {
+                        mPrefetchOutput.add(childInfo);
+                    }
+                    if (mPrefetcher.shouldStopPrefetching(mPrefetchOutput)) {
+                        return;
+                    }
+                    // Add children to deque.
+                    child.addChildren(childInfo, this);
+                }
+            } finally {
+                clear();
+            }
+        }
+
+        E getNext() {
+            if (isStack()) {
+                return pop();
+            }
+            return removeLast();
+        }
+
+        boolean isStack() {
+            return (mStrategy & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST) != 0;
+        }
+    }
+
+    interface DequeNode {
+        AccessibilityNodeInfo getA11yNodeInfo();
+        void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque);
+    }
+
+    private class ViewNode implements DequeNode {
+        View mView;
+        private final ArrayList<View> mTempViewList = new ArrayList<>();
+
+        ViewNode(View view) {
+            mView = view;
+        }
+
+        @Override
+        public AccessibilityNodeInfo getA11yNodeInfo() {
+            if (mView == null) {
+                return null;
+            }
+            return mView.createAccessibilityNodeInfo();
+        }
+
+        @Override
+        public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) {
+            if (mView == null) {
+                return;
+            }
+            if (!(mView instanceof ViewGroup)) {
+                return;
+            }
+            ArrayList<View> children = mTempViewList;
+            children.clear();
+            try {
+                mView.addChildrenForAccessibility(children);
+                final int childCount = children.size();
+
+                if (deque.isStack()) {
+                    for (int i = childCount - 1; i >= 0; i--) {
+                        addChild(deque, children.get(i));
+                    }
+                } else {
+                    for (int i = 0; i < childCount; i++) {
+                        addChild(deque, children.get(i));
+                    }
+                }
+            } finally {
+                children.clear();
+            }
+        }
+
+        private void addChild(ArrayDeque deque, View child) {
+            if (isShown(child)) {
+                AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+                if (provider == null) {
+                    deque.push(new ViewNode(child));
+                } else {
+                    deque.push(new VirtualNode(AccessibilityNodeProvider.HOST_VIEW_ID,
+                            provider));
+                }
+            }
+        }
+    }
+
+    private class VirtualNode implements DequeNode {
+        long mInfoId;
+        AccessibilityNodeProvider mProvider;
+
+        VirtualNode(long id, AccessibilityNodeProvider provider) {
+            mInfoId = id;
+            mProvider = provider;
+        }
+        @Override
+        public AccessibilityNodeInfo getA11yNodeInfo() {
+            if (mProvider == null) {
+                return null;
+            }
+            return mProvider.createAccessibilityNodeInfo(
+                    AccessibilityNodeInfo.getVirtualDescendantId(mInfoId));
+        }
+
+        @Override
+        public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) {
+            if (virtualRoot == null) {
+                return;
+            }
+            final int childCount = virtualRoot.getChildCount();
+            if (deque.isStack()) {
+                for (int i = childCount - 1; i >= 0; i--) {
+                    final long childNodeId = virtualRoot.getChildId(i);
+                    deque.push(new VirtualNode(childNodeId, mProvider));
+                }
+            } else {
+                for (int i = 0; i < childCount; i++) {
+                    final long childNodeId = virtualRoot.getChildId(i);
+                    deque.push(new VirtualNode(childNodeId, mProvider));
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 246a8c9..7d8e998 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -42,6 +42,7 @@
 import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1856,6 +1857,19 @@
     }
 
     /**
+     * Returns whether/how the specified display supports DISPLAY_DECORATION.
+     *
+     * Composition.DISPLAY_DECORATION is a special layer type which is used to
+     * render the screen decorations (i.e. antialiased rounded corners and
+     * cutouts) while taking advantage of specific hardware.
+     *
+     * @hide
+     */
+    public DisplayDecorationSupport getDisplayDecorationSupport() {
+        return mGlobal.getDisplayDecorationSupport(mDisplayId);
+    }
+
+    /**
      * A mode supported by a given display.
      *
      * @see Display#getSupportedModes()
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index c87c13d..c1413be 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -24,6 +24,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 
 /**
  * Initiates handwriting mode once it detects stylus movement in handwritable areas.
@@ -58,6 +61,7 @@
     private final long mTapTimeoutInMillis;
 
     private State mState = new State();
+    private final HandwritingAreaTracker mHandwritingAreasTracker = new HandwritingAreaTracker();
 
     /**
      * Helper method to reset the internal state of this class.
@@ -83,8 +87,8 @@
     private final InputMethodManager mImm;
 
     @VisibleForTesting
-    public HandwritingInitiator(ViewConfiguration viewConfiguration,
-            InputMethodManager inputMethodManager) {
+    public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration,
+            @NonNull InputMethodManager inputMethodManager) {
         mTouchSlop = viewConfiguration.getScaledTouchSlop();
         mTapTimeoutInMillis = ViewConfiguration.getTapTimeout();
         mImm = inputMethodManager;
@@ -98,7 +102,7 @@
      * @param motionEvent the stylus MotionEvent.
      */
     @VisibleForTesting
-    public void onTouchEvent(MotionEvent motionEvent) {
+    public void onTouchEvent(@NonNull MotionEvent motionEvent) {
         final int maskedAction = motionEvent.getActionMasked();
         switch (maskedAction) {
             case MotionEvent.ACTION_DOWN:
@@ -151,11 +155,20 @@
                 final float y = motionEvent.getY(pointerIndex);
                 if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) {
                     mState.mExceedTouchSlop = true;
-                    tryStartHandwriting();
+                    View candidateView =
+                            findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY);
+                    if (candidateView != null) {
+                        if (candidateView == getConnectedView()) {
+                            startHandwriting(candidateView);
+                        } else {
+                            candidateView.requestFocus();
+                        }
+                    }
                 }
         }
     }
 
+    @Nullable
     private View getConnectedView() {
         if (mConnectedView == null) return null;
         return mConnectedView.get();
@@ -178,13 +191,16 @@
             clearConnectedView();
             return;
         }
+
         final View connectedView = getConnectedView();
         if (connectedView == view) {
             ++mConnectionCount;
         } else {
             mConnectedView = new WeakReference<>(view);
             mConnectionCount = 1;
-            tryStartHandwriting();
+            if (mState.mShouldInitHandwriting) {
+                tryStartHandwriting();
+            }
         }
     }
 
@@ -233,17 +249,10 @@
             return;
         }
 
-        final ViewParent viewParent = connectedView.getParent();
-        // Do a final check before startHandwriting.
-        if (viewParent != null && connectedView.isAttachedToWindow()) {
-            final Rect editorBounds =
-                    new Rect(0, 0, connectedView.getWidth(), connectedView.getHeight());
-            if (viewParent.getChildVisibleRect(connectedView, editorBounds, null)) {
-                final int roundedInitX = Math.round(mState.mStylusDownX);
-                final int roundedInitY = Math.round(mState.mStylusDownY);
-                if (editorBounds.contains(roundedInitX, roundedInitY)) {
-                    startHandwriting(connectedView);
-                }
+        Rect handwritingArea = getViewHandwritingArea(connectedView);
+        if (handwritingArea != null) {
+            if (contains(handwritingArea, mState.mStylusDownX, mState.mStylusDownY)) {
+                startHandwriting(connectedView);
             }
         }
         reset();
@@ -251,10 +260,79 @@
 
     /** For test only. */
     @VisibleForTesting
-    public void startHandwriting(View view) {
+    public void startHandwriting(@NonNull View view) {
         mImm.startStylusHandwriting(view);
     }
 
+    /**
+     * Notify that the handwriting area for the given view might be updated.
+     * @param view the view whose handwriting area might be updated.
+     */
+    public void updateHandwritingAreasForView(@NonNull View view) {
+        mHandwritingAreasTracker.updateHandwritingAreaForView(view);
+    }
+
+    /**
+     * Given the location of the stylus event, return the best candidate view to initialize
+     * handwriting mode.
+     *
+     * @param x the x coordinates of the stylus event, in the coordinates of the window.
+     * @param y the y coordinates of the stylus event, in the coordinates of the window.
+     */
+    @Nullable
+    private View findBestCandidateView(float x, float y) {
+        // If the connectedView is not null and do not set any handwriting area, it will check
+        // whether the connectedView's boundary contains the initial stylus position. If true,
+        // directly return the connectedView.
+        final View connectedView = getConnectedView();
+        if (connectedView != null && connectedView.isAutoHandwritingEnabled()) {
+            final Rect handwritingArea = getViewHandwritingArea(connectedView);
+            if (handwritingArea != null && contains(handwritingArea, x, y)) {
+                return connectedView;
+            }
+        }
+
+        // Check the registered handwriting areas.
+        final List<HandwritableViewInfo> handwritableViewInfos =
+                mHandwritingAreasTracker.computeViewInfos();
+        for (HandwritableViewInfo viewInfo : handwritableViewInfos) {
+            final View view = viewInfo.getView();
+            if (!view.isAutoHandwritingEnabled()) continue;
+            final Rect rect = viewInfo.getHandwritingArea();
+            if (rect != null && contains(rect, x, y)) {
+                return viewInfo.getView();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return the handwriting area of the given view, represented in the window's coordinate.
+     * If the view didn't set any handwriting area, it will return the view's boundary.
+     * It will return null if the view or its handwriting area is not visible.
+     */
+    @Nullable
+    private static Rect getViewHandwritingArea(@NonNull View view) {
+        final ViewParent viewParent = view.getParent();
+        if (viewParent != null && view.isAttachedToWindow() && view.isAggregatedVisible()) {
+            Rect handwritingArea = view.getHandwritingArea();
+            if (handwritingArea == null) {
+                handwritingArea = new Rect(0, 0, view.getWidth(), view.getHeight());
+            }
+            if (viewParent.getChildVisibleRect(view, handwritingArea, null)) {
+                return handwritingArea;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return true if the (x, y) is inside by the given {@link Rect}.
+     */
+    private boolean contains(@NonNull Rect rect, float x, float y) {
+        return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
+    }
+
     private boolean largerThanTouchSlop(float x1, float y1, float x2, float y2) {
         float dx = x1 - x2;
         float dy = y1 - y2;
@@ -291,4 +369,134 @@
         private float mStylusDownX = Float.NaN;
         private float mStylusDownY = Float.NaN;
     }
+
+    /** The helper method to check if the given view is still active for handwriting. */
+    private static boolean isViewActive(@Nullable View view) {
+        return view != null && view.isAttachedToWindow() && view.isAggregatedVisible()
+                && view.isAutoHandwritingEnabled();
+    }
+
+    /**
+     * A class used to track the handwriting areas set by the Views.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class HandwritingAreaTracker {
+        private final List<HandwritableViewInfo> mHandwritableViewInfos;
+
+        public HandwritingAreaTracker() {
+            mHandwritableViewInfos = new ArrayList<>();
+        }
+
+        /**
+         * Notify this tracker that the handwriting area of the given view has been updated.
+         * This method does three things:
+         * a) iterate over the all the tracked ViewInfos and remove those already invalid ones.
+         * b) mark the given view's ViewInfo to be dirty. So that next time when
+         * {@link #computeViewInfos} is called, this view's handwriting area will be recomputed.
+         * c) If no the given view is not in the tracked ViewInfo list, a new ViewInfo object will
+         * be created and added to the list.
+         *
+         * @param view the view whose handwriting area is updated.
+         */
+        public void updateHandwritingAreaForView(@NonNull View view) {
+            Iterator<HandwritableViewInfo> iterator = mHandwritableViewInfos.iterator();
+            boolean found = false;
+            while (iterator.hasNext()) {
+                final HandwritableViewInfo handwritableViewInfo = iterator.next();
+                final View curView = handwritableViewInfo.getView();
+                if (!isViewActive(curView)) {
+                    iterator.remove();
+                }
+                if (curView == view) {
+                    found = true;
+                    handwritableViewInfo.mIsDirty = true;
+                }
+            }
+            if (!found && isViewActive(view)) {
+                // The given view is not tracked. Create a new HandwritableViewInfo for it and add
+                // to the list.
+                mHandwritableViewInfos.add(new HandwritableViewInfo(view));
+            }
+        }
+
+        /**
+         * Update the handwriting areas and return a list of ViewInfos containing the view
+         * reference and its handwriting area.
+         */
+        @NonNull
+        public List<HandwritableViewInfo> computeViewInfos() {
+            mHandwritableViewInfos.removeIf(viewInfo -> !viewInfo.update());
+            return mHandwritableViewInfos;
+        }
+    }
+
+    /**
+     * A class that reference to a View and its handwriting area(in the ViewRoot's coordinate.)
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class HandwritableViewInfo {
+        final WeakReference<View> mViewRef;
+        Rect mHandwritingArea = null;
+        @VisibleForTesting
+        public boolean mIsDirty = true;
+
+        @VisibleForTesting
+        public HandwritableViewInfo(@NonNull View view) {
+            mViewRef = new WeakReference<>(view);
+        }
+
+        /** Return the tracked view. */
+        @Nullable
+        public View getView() {
+            return mViewRef.get();
+        }
+
+        /**
+         * Return the tracked handwriting area, represented in the ViewRoot's coordinates.
+         * Notice, the caller should not modify the returned Rect.
+         */
+        @Nullable
+        public Rect getHandwritingArea() {
+            return mHandwritingArea;
+        }
+
+        /**
+         * Update the handwriting area in this ViewInfo.
+         *
+         * @return true if this ViewInfo is still valid. Or false if this ViewInfo has become
+         * invalid due to either view is no longer visible, or the handwriting area set by the
+         * view is removed. {@link HandwritingAreaTracker} no longer need to keep track of this
+         * HandwritableViewInfo this method returns false.
+         */
+        public boolean update() {
+            final View view = getView();
+            if (!isViewActive(view)) {
+                return false;
+            }
+
+            if (!mIsDirty) {
+                return true;
+            }
+            final Rect localRect = view.getHandwritingArea();
+            if (localRect == null) {
+                return false;
+            }
+
+            ViewParent parent = view.getParent();
+            if (parent != null) {
+                final Rect newRect = new Rect(localRect);
+                if (parent.getChildVisibleRect(view, newRect, null /* offset */)) {
+                    mHandwritingArea = newRect;
+                } else {
+                    mHandwritingArea = null;
+                }
+            }
+            mIsDirty = false;
+            return true;
+        }
+    }
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 32054b1..1ed35f7 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -298,7 +298,7 @@
     */
     void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
             in IBinder hostInputToken, int flags, int privateFlags, int type,
-            in IBinder focusGrantToken, out InputChannel outInputChannel);
+            in IBinder focusGrantToken, String inputHandleName, out InputChannel outInputChannel);
 
     /**
      * Update the flags on an input channel associated with a particular surface.
diff --git a/core/java/android/view/OnBackInvokedCallback.java b/core/java/android/view/OnBackInvokedCallback.java
index b5cd89c..37f858a 100644
--- a/core/java/android/view/OnBackInvokedCallback.java
+++ b/core/java/android/view/OnBackInvokedCallback.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.app.Dialog;
+import android.window.BackEvent;
 
 /**
  * Interface for applications to register back invocation callbacks. This allows the client
@@ -46,14 +47,12 @@
     /**
      * Called on back gesture progress.
      *
-     * @param touchX Absolute X location of the touch point.
-     * @param touchY Absolute Y location of the touch point.
-     * @param progress Value between 0 and 1 on how far along the back gesture is.
+     * @param backEvent An {@link android.window.BackEvent} object describing the progress event.
      *
+     * @see android.window.BackEvent
      * @hide
      */
-    // TODO(b/210539672): combine back progress params into BackEvent.
-    default void onBackProgressed(int touchX, int touchY, float progress) { };
+    default void onBackProgressed(BackEvent backEvent) { };
 
     /**
      * Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d3061e2..ce54968 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -49,6 +49,7 @@
 import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -235,7 +236,8 @@
             float shadowRadius);
     private static native void nativeSetGlobalShadowSettings(@Size(4) float[] ambientColor,
             @Size(4) float[] spotColor, float lightPosY, float lightPosZ, float lightRadius);
-    private static native boolean nativeGetDisplayDecorationSupport(IBinder displayToken);
+    private static native DisplayDecorationSupport nativeGetDisplayDecorationSupport(
+            IBinder displayToken);
 
     private static native void nativeSetFrameRate(long transactionObj, long nativeObject,
             float frameRate, int compatibility, int changeFrameRateStrategy);
@@ -2694,16 +2696,18 @@
     }
 
     /**
-     * Returns whether a display supports DISPLAY_DECORATION.
+     * Returns whether/how a display supports DISPLAY_DECORATION.
      *
      * @param displayToken
      *      The token for the display.
      *
-     * @return Whether the display supports DISPLAY_DECORATION.
+     * @return A class describing how the display supports DISPLAY_DECORATION or null if it does
+     * not.
      *
+     * TODO (b/218524164): Move this out of SurfaceControl.
      * @hide
      */
-    public static boolean getDisplayDecorationSupport(IBinder displayToken) {
+    public static DisplayDecorationSupport getDisplayDecorationSupport(IBinder displayToken) {
         return nativeGetDisplayDecorationSupport(displayToken);
     }
 
@@ -3696,21 +3700,32 @@
         /**
          * Sets the buffer transform that should be applied to the current buffer.
          *
+         * This can be used in combination with
+         * {@link AttachedSurfaceControl#addOnBufferTransformHintChangedListener(AttachedSurfaceControl.OnBufferTransformHintChangedListener)}
+         * to pre-rotate the buffer for the current display orientation. This can
+         * improve the performance of displaying the associated buffer.
+         *
          * @param sc The SurfaceControl to update
          * @param transform The transform to apply to the buffer.
          * @return this
          */
         public @NonNull Transaction setBufferTransform(@NonNull SurfaceControl sc,
-                /* TODO: Mark the intdef */ int transform) {
+                @SurfaceControl.BufferTransform int transform) {
             checkPreconditions(sc);
             nativeSetBufferTransform(mNativeObject, sc.mNativeObject, transform);
             return this;
         }
 
         /**
-         * Updates the region for the content on this surface updated in this transaction.
+         * Updates the region for the content on this surface updated in this transaction. The
+         * damage region is the area of the buffer that has changed since the previously
+         * sent buffer. This can be used to reduce the amount of recomposition that needs
+         * to happen when only a small region of the buffer is being updated, such as for
+         * a small blinking cursor or a loading indicator.
          *
-         * If unspecified, the complete surface is assumed to be damaged.
+         * @param sc The SurfaceControl on which to set the damage region
+         * @param region The region to set. If null, the entire buffer is assumed dirty. This is
+         *               equivalent to not setting a damage region at all.
          */
         public @NonNull Transaction setDamageRegion(@NonNull SurfaceControl sc,
                 @Nullable Region region) {
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 7e0d887..2edfda5 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -27,8 +27,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.util.Log;
 import android.view.InsetsState;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
 
 import java.util.Objects;
 
@@ -43,11 +45,13 @@
  * {@link SurfaceView#setChildSurfacePackage}.
  */
 public class SurfaceControlViewHost {
+    private final static String TAG = "SurfaceControlViewHost";
     private final ViewRootImpl mViewRoot;
     private WindowlessWindowManager mWm;
 
     private SurfaceControl mSurfaceControl;
     private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
+    private boolean mReleased = false;
 
     private final class ISurfaceControlViewHostImpl extends ISurfaceControlViewHost.Stub {
         @Override
@@ -268,6 +272,8 @@
             @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
         mWm = wwm;
         mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer);
+        WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
+
         mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
     }
 
@@ -292,7 +298,10 @@
                 .build();
         mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
                 mSurfaceControl, hostToken);
+
         mViewRoot = new ViewRootImpl(context, display, mWm);
+        WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
+
         mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
     }
 
@@ -301,12 +310,15 @@
      */
     @Override
     protected void finalize() throws Throwable {
-        // We aren't on the UI thread here so we need to pass false to
-        // doDie
+        if (mReleased) {
+            return;
+        }
+        Log.e(TAG, "SurfaceControlViewHost finalized without being released: " + this);
+        // We aren't on the UI thread here so we need to pass false to doDie
         mViewRoot.die(false /* immediate */);
+        WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
     }
 
-
     /**
      * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy.
      * Rather than be directly reparented using {@link SurfaceControl.Transaction} this
@@ -413,5 +425,14 @@
     public void release() {
         // ViewRoot will release mSurfaceControl for us.
         mViewRoot.die(true /* immediate */);
+        WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
+        mReleased = true;
+    }
+
+    /**
+     * @hide
+     */
+    public IBinder getFocusGrantToken() {
+        return mWm.getFocusGrantToken();
     }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f74b599..179f6ee 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4745,9 +4745,11 @@
         private List<Rect> mSystemGestureExclusionRects = null;
         private List<Rect> mKeepClearRects = null;
         private boolean mPreferKeepClear = false;
+        private Rect mHandwritingArea = null;
 
         /**
-         * Used to track {@link #mSystemGestureExclusionRects} and {@link #mKeepClearRects}
+         * Used to track {@link #mSystemGestureExclusionRects}, {@link #mKeepClearRects} and
+         * {@link #mHandwritingArea}.
          */
         public RenderNode.PositionUpdateListener mPositionUpdateListener;
         private Runnable mPositionChangedUpdate;
@@ -11710,7 +11712,8 @@
     private void updatePositionUpdateListener() {
         final ListenerInfo info = getListenerInfo();
         if (getSystemGestureExclusionRects().isEmpty()
-                && collectPreferKeepClearRects().isEmpty()) {
+                && collectPreferKeepClearRects().isEmpty()
+                && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
             if (info.mPositionUpdateListener != null) {
                 mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
                 info.mPositionChangedUpdate = null;
@@ -11720,6 +11723,7 @@
                 info.mPositionChangedUpdate = () -> {
                     updateSystemGestureExclusionRects();
                     updateKeepClearRects();
+                    updateHandwritingArea();
                 };
                 info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() {
                     @Override
@@ -11876,6 +11880,51 @@
     }
 
     /**
+     * Set a list of handwriting areas in this view. If there is any stylus {@link MotionEvent}
+     * occurs within those areas, it will trigger stylus handwriting mode. This can be disabled by
+     * disabling the auto handwriting initiation by calling
+     * {@link #setAutoHandwritingEnabled(boolean)} with false.
+     *
+     * @attr rects a list of handwriting area in the view's local coordiniates.
+     *
+     * @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)
+     * @see #setAutoHandwritingEnabled(boolean)
+     *
+     * @hide
+     */
+    public void setHandwritingArea(@Nullable Rect rect) {
+        final ListenerInfo info = getListenerInfo();
+        info.mHandwritingArea = rect;
+        updatePositionUpdateListener();
+        postUpdate(this::updateHandwritingArea);
+    }
+
+    /**
+     * Return the handwriting areas set on this view, in its local coordinates.
+     * Notice: the caller of this method should not modify the Rect returned.
+     * @see #setHandwritingArea(Rect)
+     *
+     * @hide
+     */
+    @Nullable
+    public Rect getHandwritingArea() {
+        final ListenerInfo info = mListenerInfo;
+        if (info != null) {
+            return info.mHandwritingArea;
+        }
+        return null;
+    }
+
+    void updateHandwritingArea() {
+        // If autoHandwritingArea is not enabled, do nothing.
+        if (!isAutoHandwritingEnabled()) return;
+        final AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            ai.mViewRootImpl.getHandwritingInitiator().updateHandwritingAreasForView(this);
+        }
+    }
+
+    /**
      * Compute the view's coordinate within the surface.
      *
      * <p>Computes the coordinates of this view in its surface. The argument
@@ -31181,6 +31230,8 @@
         } else {
             mPrivateFlags4 &= ~PFLAG4_AUTO_HANDWRITING_ENABLED;
         }
+        updatePositionUpdateListener();
+        postUpdate(this::updateHandwritingArea);
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 39fc2c0..8236fbb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -583,6 +583,12 @@
     boolean mForceNextWindowRelayout;
     CountDownLatch mWindowDrawCountDown;
 
+    // Whether we have used applyTransactionOnDraw to schedule an RT
+    // frame callback consuming a passed in transaction. In this case
+    // we also need to schedule a commit callback so we can observe
+    // if the draw was skipped, and the BBQ pending transactions.
+    boolean mHasPendingTransactions;
+
     boolean mIsDrawing;
     int mLastSystemUiVisibility;
     int mClientWindowLayoutFlags;
@@ -1788,10 +1794,16 @@
     }
 
     void pokeDrawLockIfNeeded() {
-        final int displayState = mAttachInfo.mDisplayState;
-        if (mView != null && mAdded && mTraversalScheduled
-                && (displayState == Display.STATE_DOZE
-                        || displayState == Display.STATE_DOZE_SUSPEND)) {
+        if (!Display.isDozeState(mAttachInfo.mDisplayState)) {
+            // Only need to acquire wake lock for DOZE state.
+            return;
+        }
+        if (mWindowAttributes.type != WindowManager.LayoutParams.TYPE_BASE_APPLICATION) {
+            // Non-activity windows should be responsible to hold wake lock by themself, because
+            // usually they are system windows.
+            return;
+        }
+        if (mAdded && mTraversalScheduled && mAttachInfo.mHasWindowFocus) {
             try {
                 mWindowSession.pokeDrawLock(mWindow);
             } catch (RemoteException ex) {
@@ -4184,6 +4196,9 @@
                         mRtLastAttemptedDrawFrameNum);
                 tmpTransaction.merge(pendingTransactions);
             }
+            if (!useBlastSync && !reportNextDraw) {
+                tmpTransaction.apply();
+            }
 
             // Post at front of queue so the buffer can be processed immediately and allow RT
             // to continue processing new buffers. If RT tries to process buffers before the sync
@@ -4214,7 +4229,7 @@
         final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
         final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
 
-        if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw) {
+        if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw && !mHasPendingTransactions) {
             return null;
         }
 
@@ -4226,11 +4241,14 @@
                     + " nextDrawUseBlastSync=" + useBlastSync
                     + " reportNextDraw=" + reportNextDraw
                     + " hasBlurUpdates=" + hasBlurUpdates
-                    + " hasBlastSyncConsumer=" + (blastSyncConsumer != null));
+                    + " hasBlastSyncConsumer=" + (blastSyncConsumer != null)
+                    + " mHasPendingTransactions=" + mHasPendingTransactions);
         }
 
         final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
                 needsCallbackForBlur ?  mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;
+        final boolean hasPendingTransactions = mHasPendingTransactions;
+        mHasPendingTransactions = false;
 
         // The callback will run on the render thread.
         return new FrameDrawingCallback() {
@@ -4257,7 +4275,7 @@
                     return null;
                 }
 
-                if (!useBlastSync && !reportNextDraw) {
+                if (!useBlastSync && !reportNextDraw && !hasPendingTransactions) {
                     return null;
                 }
 
@@ -10700,7 +10718,10 @@
         if (mRemoved || !isHardwareEnabled()) {
             t.apply();
         } else {
-            registerRtFrameCallback(frame -> mergeWithNextTransaction(t, frame));
+            mHasPendingTransactions = true;
+            registerRtFrameCallback(frame -> {
+                mergeWithNextTransaction(t, frame);
+            });
         }
         return true;
     }
@@ -10841,6 +10862,7 @@
     private void unregisterCompatOnBackInvokedCallback() {
         if (mCompatOnBackInvokedCallback != null) {
             mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mCompatOnBackInvokedCallback);
+            mCompatOnBackInvokedCallback = null;
         }
     }
 
@@ -10854,4 +10876,8 @@
         mLastGivenInsets.reset();
         requestLayout();
     }
+
+    IWindowSession getWindowSession() {
+        return mWindowSession;
+    }
 }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c92a3a0..93cb0dd7 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -159,6 +159,8 @@
             new ArrayList<WindowManager.LayoutParams>();
     private final ArraySet<View> mDyingViews = new ArraySet<View>();
 
+    private final ArrayList<ViewRootImpl> mWindowlessRoots = new ArrayList<ViewRootImpl>();
+
     private Runnable mSystemPropertyUpdater;
 
     private WindowManagerGlobal() {
@@ -387,7 +389,25 @@
                 }
             }
 
-            root = new ViewRootImpl(view.getContext(), display);
+            IWindowSession windowlessSession = null;
+            // If there is a parent set, but we can't find it, it may be coming
+            // from a SurfaceControlViewHost hierarchy.
+            if (wparams.token != null && panelParentView == null) {
+                for (int i = 0; i < mWindowlessRoots.size(); i++) {
+                    ViewRootImpl maybeParent = mWindowlessRoots.get(i);
+                    if (maybeParent.getWindowToken() == wparams.token) {
+                        windowlessSession = maybeParent.getWindowSession();
+                        break;
+                    }
+                }
+            }
+
+            if (windowlessSession == null) {
+                root = new ViewRootImpl(view.getContext(), display);
+            } else {
+                root = new ViewRootImpl(view.getContext(), display,
+                        windowlessSession);
+            }
 
             view.setLayoutParams(wparams);
 
@@ -720,6 +740,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** @hide */
+    public void addWindowlessRoot(ViewRootImpl impl) {
+        synchronized (mLock) {
+            mWindowlessRoots.add(impl);
+        }
+    }
+
+    /** @hide */
+    public void removeWindowlessRoot(ViewRootImpl impl) {
+        synchronized (mLock) {
+            mWindowlessRoots.remove(impl);
+        }
+    }
 }
 
 final class WindowLeaked extends AndroidRuntimeException {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 56f0915..2122152 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -160,14 +160,15 @@
         if (((attrs.inputFeatures &
                 WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) {
             try {
-                if(mRealWm instanceof IWindowSession.Stub) {
+                if (mRealWm instanceof IWindowSession.Stub) {
                     mRealWm.grantInputChannel(displayId,
-                        new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
-                        window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
-                        mFocusGrantToken, outInputChannel);
+                            new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
+                            window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
+                            mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
                 } else {
                     mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
-                        attrs.privateFlags, attrs.type, mFocusGrantToken, outInputChannel);
+                            attrs.privateFlags, attrs.type, mFocusGrantToken,
+                            attrs.getTitle().toString(), outInputChannel);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to grant input to surface: ", e);
@@ -485,7 +486,7 @@
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
             IBinder hostInputToken, int flags, int privateFlags, int type, IBinder focusGrantToken,
-            InputChannel outInputChannel) {
+            String inputHandleName, InputChannel outInputChannel) {
     }
 
     @Override
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 82e823f..07b7a18 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -19,6 +19,8 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
 import static android.os.Build.VERSION_CODES.S;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.annotation.NonNull;
@@ -301,10 +303,11 @@
      * @param connectionId The id of a connection for interacting with the system.
      * @return The root {@link AccessibilityNodeInfo} if found, null otherwise.
      */
-    public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
+    public AccessibilityNodeInfo getRootInActiveWindow(int connectionId,
+            @AccessibilityNodeInfo.PrefetchingStrategy int strategy) {
         return findAccessibilityNodeInfoByAccessibilityId(connectionId,
                 AccessibilityWindowInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
-                false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
+                false, strategy, null);
     }
 
     /**
@@ -529,11 +532,6 @@
     public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
             int connectionId, int accessibilityWindowId, long accessibilityNodeId,
             boolean bypassCache, int prefetchFlags, Bundle arguments) {
-        if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0
-                && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) {
-            throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS"
-                + " requires FLAG_PREFETCH_PREDECESSORS");
-        }
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
@@ -560,7 +558,7 @@
                         }
                         if (!cache.isEnabled()) {
                             // Skip prefetching if cache is disabled.
-                            prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+                            prefetchFlags &= ~FLAG_PREFETCH_MASK;
                         }
                         if (DEBUG) {
                             Log.i(LOG_TAG, "Node cache miss for "
@@ -573,12 +571,18 @@
                     }
                 } else {
                     // No need to prefech nodes in bypass cache case.
-                    prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+                    prefetchFlags &= ~FLAG_PREFETCH_MASK;
                 }
                 // Skip prefetching if window is scrolling.
-                if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
+                if ((prefetchFlags & FLAG_PREFETCH_MASK) != 0
                         && isWindowScrolling(accessibilityWindowId)) {
-                    prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+                    prefetchFlags &= ~FLAG_PREFETCH_MASK;
+                }
+
+                final int descendantPrefetchFlags = prefetchFlags & FLAG_PREFETCH_DESCENDANTS_MASK;
+                if ((descendantPrefetchFlags & (descendantPrefetchFlags - 1)) != 0) {
+                    throw new IllegalArgumentException("There can be no more than one descendant"
+                            + " prefetching strategy");
                 }
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 if (shouldTraceClient()) {
@@ -599,21 +603,41 @@
                     Binder.restoreCallingIdentity(identityToken);
                 }
                 if (packageNames != null) {
-                    AccessibilityNodeInfo info =
-                            getFindAccessibilityNodeInfoResultAndClear(interactionId);
-                    if (shouldTraceCallback()) {
-                        logTraceCallback(connection, "findAccessibilityNodeInfoByAccessibilityId",
-                                "InteractionId:" + interactionId + ";connectionId="
-                                + connectionId + ";Result: " + info);
+                    if ((prefetchFlags
+                            & AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE) != 0) {
+                        List<AccessibilityNodeInfo> infos =
+                                getFindAccessibilityNodeInfosResultAndClear(
+                                interactionId);
+                        if (shouldTraceCallback()) {
+                            logTraceCallback(connection,
+                                    "findAccessibilityNodeInfoByAccessibilityId",
+                                    "InteractionId:" + interactionId + ";connectionId="
+                                            + connectionId + ";Result: " + infos);
+                        }
+                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
+                                bypassCache, packageNames);
+                        if (infos != null && !infos.isEmpty()) {
+                            return infos.get(0);
+                        }
+                    } else {
+                        AccessibilityNodeInfo info =
+                                getFindAccessibilityNodeInfoResultAndClear(interactionId);
+                        if (shouldTraceCallback()) {
+                            logTraceCallback(connection,
+                                    "findAccessibilityNodeInfoByAccessibilityId",
+                                    "InteractionId:" + interactionId + ";connectionId="
+                                            + connectionId + ";Result: " + info);
+                        }
+                        if ((prefetchFlags & FLAG_PREFETCH_MASK) != 0
+                                && info != null) {
+                            setInteractionWaitingForPrefetchResult(interactionId, connectionId,
+                                    packageNames);
+                        }
+                        finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
+                                bypassCache, packageNames);
+                        return info;
                     }
-                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
-                            && info != null) {
-                        setInteractionWaitingForPrefetchResult(interactionId, connectionId,
-                                packageNames);
-                    }
-                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
-                            bypassCache, packageNames);
-                    return info;
+
                 }
             } else {
                 if (DEBUG) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index a31cacf..aeef76c 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -23,8 +23,10 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
@@ -62,6 +64,8 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -128,23 +132,99 @@
     public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
             AccessibilityNodeProvider.HOST_VIEW_ID);
 
-    /** @hide */
-    public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
+    /**
+     * Prefetching strategy that prefetches the ancestors of the requested node.
+     * <p> Ancestors will be prefetched before siblings and descendants.
+     *
+     * @see #getChild(int, int)
+     * @see #getParent(int)
+     * @see AccessibilityWindowInfo#getRoot(int)
+     * @see AccessibilityService#getRootInActiveWindow(int)
+     * @see AccessibilityEvent#getSource(int)
+     */
+    public static final int FLAG_PREFETCH_ANCESTORS = 0x00000001;
 
-    /** @hide */
+    /**
+     * Prefetching strategy that prefetches the siblings of the requested node.
+     * <p> To avoid disconnected trees, this flag will also prefetch the parent. Siblings will be
+     * prefetched before descendants.
+     *
+     * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+     */
     public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
 
-    /** @hide */
-    public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
+    /**
+     * Prefetching strategy that prefetches the descendants in a hybrid depth first and breadth
+     * first approach.
+     * <p> The children of the root node is prefetched before recursing on the children. This
+     * must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST} or
+     * {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an
+     * IllegalArgumentException.
+     *
+     * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+     */
+    public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 0x00000004;
+
+    /**
+     * Prefetching strategy that prefetches the descendants of the requested node depth-first.
+     * <p> This must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_HYBRID} or
+     * {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an
+     * IllegalArgumentException.
+     *
+     * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+     */
+    public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 0x00000008;
+
+    /**
+     * Prefetching strategy that prefetches the descendants of the requested node breadth-first.
+     * <p> This must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_HYBRID} or
+     * {@link #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST} or this will trigger an
+     * IllegalArgumentException.
+     *
+     * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+     */
+    public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 0x00000010;
+
+    /**
+     * Prefetching flag that specifies prefetching should not be interrupted by a request to
+     * retrieve a node or perform an action on a node.
+     *
+     * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+     */
+    public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 0x00000020;
 
     /** @hide */
-    public static final int FLAG_PREFETCH_MASK = 0x00000007;
+    public static final int FLAG_PREFETCH_MASK = 0x0000003f;
 
     /** @hide */
-    public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+    public static final int FLAG_PREFETCH_DESCENDANTS_MASK = 0x0000001C;
+
+    /**
+     * Maximum batch size of prefetched nodes for a request.
+     */
+    @SuppressLint("MinMaxConstant")
+    public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50;
 
     /** @hide */
-    public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
+    @IntDef(flag = true, prefix = { "FLAG_PREFETCH" }, value = {
+            FLAG_PREFETCH_ANCESTORS,
+            FLAG_PREFETCH_SIBLINGS,
+            FLAG_PREFETCH_DESCENDANTS_HYBRID,
+            FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST,
+            FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST,
+            FLAG_PREFETCH_UNINTERRUPTIBLE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PrefetchingStrategy {}
+
+    /** @hide */
+    public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
+
+    /** @hide */
+    public static final int FLAG_REPORT_VIEW_IDS = 0x00000100;
+
+    /** @hide */
+    public static final int FLAG_REPORT_MASK = 0x00000180;
 
     // Actions.
 
@@ -1079,11 +1159,6 @@
 
     /**
      * Get the child at given index.
-     * <p>
-     *   <strong>Note:</strong> It is a client responsibility to recycle the
-     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
-     *     to avoid creating of multiple instances.
-     * </p>
      *
      * @param index The child index.
      * @return The child node.
@@ -1092,6 +1167,23 @@
      *
      */
     public AccessibilityNodeInfo getChild(int index) {
+        return getChild(index, FLAG_PREFETCH_DESCENDANTS_HYBRID);
+    }
+
+
+    /**
+     * Get the child at given index.
+     *
+     * @param index The child index.
+     * @param prefetchingStrategy the prefetching strategy.
+     * @return The child node.
+     *
+     * @throws IllegalStateException If called outside of an AccessibilityService.
+     *
+     * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+     */
+    @Nullable
+    public AccessibilityNodeInfo getChild(int index, @PrefetchingStrategy int prefetchingStrategy) {
         enforceSealed();
         if (mChildNodeIds == null) {
             return null;
@@ -1103,11 +1195,11 @@
         final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
             return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
-                    ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
+                    ROOT_NODE_ID, false, prefetchingStrategy, null);
         }
 
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
-                childId, false, FLAG_PREFETCH_DESCENDANTS, null);
+                childId, false, prefetchingStrategy, null);
     }
 
     /**
@@ -1816,23 +1908,56 @@
 
     /**
      * Gets the parent.
-     * <p>
-     *   <strong>Note:</strong> It is a client responsibility to recycle the
-     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
-     *     to avoid creating of multiple instances.
-     * </p>
      *
      * @return The parent.
      */
     public AccessibilityNodeInfo getParent() {
         enforceSealed();
         if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
-            return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
+            return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId,
+                    FLAG_PREFETCH_ANCESTORS | FLAG_PREFETCH_SIBLINGS);
         }
         return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
     }
 
     /**
+     * Gets the parent.
+     *
+     * <p>
+     * Use {@code prefetchingStrategy} to determine the types of
+     * nodes prefetched from the app if the requested node is not in the cache and must be retrieved
+     * by the app. The default strategy for {@link #getParent()} is a combination of ancestor and
+     * sibling strategies. The app will prefetch until all nodes fulfilling the strategies are
+     * fetched, another node request is sent, or the maximum prefetch batch size of
+     * {@link #MAX_NUMBER_OF_PREFETCHED_NODES} nodes is reached. To prevent interruption by another
+     * request and to force prefetching of the max batch size, use
+     * {@link AccessibilityNodeInfo#FLAG_PREFETCH_UNINTERRUPTIBLE}.
+     * </p>
+     *
+     * @param prefetchingStrategy the prefetching strategy.
+     * @return The parent.
+     *
+     * @throws IllegalStateException If called outside of an AccessibilityService.
+     *
+     * @see #FLAG_PREFETCH_ANCESTORS
+     * @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
+     * @see #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST
+     * @see #FLAG_PREFETCH_DESCENDANTS_HYBRID
+     * @see #FLAG_PREFETCH_SIBLINGS
+     * @see #FLAG_PREFETCH_UNINTERRUPTIBLE
+     */
+    @Nullable
+    public AccessibilityNodeInfo getParent(@PrefetchingStrategy int prefetchingStrategy) {
+        enforceSealed();
+        if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
+            return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId,
+                    prefetchingStrategy);
+        }
+        return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId,
+                prefetchingStrategy);
+    }
+
+    /**
      * @return The parent node id.
      *
      * @hide
@@ -4507,17 +4632,31 @@
 
     private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
             int windowId, long accessibilityId) {
+        return getNodeForAccessibilityId(connectionId, windowId, accessibilityId,
+                FLAG_PREFETCH_ANCESTORS
+                        | FLAG_PREFETCH_DESCENDANTS_HYBRID | FLAG_PREFETCH_SIBLINGS);
+    }
+
+    private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+            int windowId, long accessibilityId, @PrefetchingStrategy int prefetchingStrategy) {
         if (!canPerformRequestOverConnection(connectionId, windowId, accessibilityId)) {
             return null;
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
-                windowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
-                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+                windowId, accessibilityId, false, prefetchingStrategy, null);
     }
 
     private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
             IBinder leashToken, long accessibilityId) {
+        return getNodeForAccessibilityId(connectionId, leashToken, accessibilityId,
+                FLAG_PREFETCH_ANCESTORS
+                        | FLAG_PREFETCH_DESCENDANTS_HYBRID | FLAG_PREFETCH_SIBLINGS);
+    }
+
+    private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+            IBinder leashToken, long accessibilityId,
+            @PrefetchingStrategy int prefetchingStrategy) {
         if (!((leashToken != null)
                 && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
                 && (connectionId != UNDEFINED_CONNECTION_ID))) {
@@ -4525,8 +4664,7 @@
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
-                leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
-                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+                leashToken, accessibilityId, false, prefetchingStrategy, null);
     }
 
     /** @hide */
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index cf2ea15..036316e 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -74,10 +74,9 @@
     private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
 
     private static final int GET_SOURCE_PREFETCH_FLAGS =
-        AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
-        | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
-        | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
-
+            AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS
+                    | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
+                    | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID;
 
     @UnsupportedAppUsage
     boolean mSealed;
@@ -185,16 +184,30 @@
      * @return The info of the source.
      */
     public @Nullable AccessibilityNodeInfo getSource() {
+        return getSource(GET_SOURCE_PREFETCH_FLAGS);
+    }
+
+    /**
+     * Gets the {@link AccessibilityNodeInfo} of the event source.
+     *
+     * @param prefetchingStrategy the prefetching strategy.
+     * @return The info of the source.
+     *
+     * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+     */
+    @Nullable
+    public AccessibilityNodeInfo getSource(
+            @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
         enforceSealed();
         if ((mConnectionId == UNDEFINED)
                 || (mSourceWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
                 || (AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId)
-                        == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) {
+                == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) {
             return null;
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
-                mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS, null);
+                mSourceNodeId, false, prefetchingStrategy, null);
     }
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 540f5dc..f155bad 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -219,13 +219,27 @@
      * @return The root node.
      */
     public AccessibilityNodeInfo getRoot() {
+        return getRoot(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
+    }
+
+    /**
+     * Gets the root node in the window's hierarchy.
+     *
+     * @param prefetchingStrategy the prefetching strategy.
+     * @return The root node.
+     *
+     * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+     */
+    @Nullable
+    public AccessibilityNodeInfo getRoot(
+            @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
         if (mConnectionId == UNDEFINED_WINDOW_ID) {
             return null;
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
                 mId, AccessibilityNodeInfo.ROOT_NODE_ID,
-                true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
+                true, prefetchingStrategy, null);
     }
 
     /**
@@ -382,6 +396,14 @@
      * Gets if this window is active. An active window is the one
      * the user is currently touching or the window has input focus
      * and the user is not touching any window.
+     * <p>
+     * This is defined as the window that most recently fired one
+     * of the following events:
+     * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED},
+     * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
+     * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}.
+     * In other words, the last window shown that also has input focus.
+     * </p>
      *
      * @return Whether this is the active window.
      */
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 60ccf67..b7994db 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3100,11 +3100,51 @@
     }
 
     /**
-     * Checks the id of autofill whether supported the fill dialog.
+     * If autofill suggestions for a dialog-style UI are available for {@code view}, shows a dialog
+     * allowing the user to select a suggestion and returns {@code true}.
+     * <p>
+     * The dialog may not be available if the autofill service does not support it, or if the
+     * autofill request has not returned a response yet.
+     * <p>
+     * It is recommended to call this method the first time a user focuses on an autofill-able form,
+     * and to avoid showing the input method if the dialog is shown. If this method returns
+     * {@code false}, you should then instead show the input method (assuming that is how the
+     * view normally handles the focus event). If the user re-focuses on the view, you should not
+     * call this method again so as to not disrupt usage of the input method.
      *
-     * @hide
+     * @param view the view for which to show autofill suggestions. This is typically a view
+     *             receiving a focus event. The autofill suggestions shown will include content for
+     *             related views as well.
+     * @return {@code true} if the autofill dialog is being shown
      */
-    public boolean isShowFillDialog(AutofillId id) {
+    // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
+    public boolean showAutofillDialog(@NonNull View view) {
+        Objects.requireNonNull(view);
+        if (shouldShowAutofillDialog(view.getAutofillId())) {
+            // If the id matches a trigger id, this will trigger the fill dialog.
+            notifyViewEntered(view);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Like {@link #showAutofillDialog(View)} but for virtual views.
+     *
+     * @param virtualId id identifying the virtual child inside the parent view.
+     */
+    // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
+    public boolean showAutofillDialog(@NonNull View view, int virtualId) {
+        Objects.requireNonNull(view);
+        if (shouldShowAutofillDialog(getAutofillId(view, virtualId))) {
+            // If the id matches a trigger id, this will trigger the fill dialog.
+            notifyViewEntered(view, virtualId, /* bounds= */ null, /* flags= */ 0);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean shouldShowAutofillDialog(AutofillId id) {
         if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) {
             return false;
         }
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 7e6e6fd..9a70667 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -278,6 +278,7 @@
                                     .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */))
                             .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable
                                     .InputMethod_Subtype_isAsciiCapable, false)).build();
+                    a.recycle();
                     if (!subtype.isAuxiliary()) {
                         isAuxIme = false;
                     }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c713a54..2359d8d 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1764,7 +1764,7 @@
     }
 
     /**
-     * This method is still kept for a while until android.support.v7.widget.SearchView ver. 26.0
+     * This method is still kept for a while until androidx.appcompat.widget.SearchView ver. 26.0
      * is publicly released because previous implementations of that class had relied on this method
      * via reflection.
      *
@@ -1777,7 +1777,7 @@
         synchronized (mH) {
             try {
                 Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
-                        + " removed soon. If you are using android.support.v7.widget.SearchView,"
+                        + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
                         + " please update to version 26.0 or newer version.");
                 if (mCurRootView == null || mCurRootView.getView() == null) {
                     Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index 13d44da..edcbce9 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -124,6 +124,7 @@
                                     .SpellChecker_Subtype_subtypeExtraValue),
                             a.getInt(com.android.internal.R.styleable
                                     .SpellChecker_Subtype_subtypeId, 0));
+                    a.recycle();
                     mSubtypes.add(subtype);
                 }
             }
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index de9f76d..f554f89 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -61,7 +61,7 @@
  * </p>
  * <p class="note"><strong>Note:</strong>
  * If you are considering using array adapter with a ListView, consider using
- * {@link android.support.v7.widget.RecyclerView} instead.
+ * {@link androidx.recyclerview.widget.RecyclerView} instead.
  * RecyclerView offers similar features with better performance and more flexibility than
  * ListView provides.
  * See the
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index ec07209..1bde235 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -209,6 +209,7 @@
             // Generate a non-activated color using the disabled alpha.
             final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
             final float disabledAlpha = ta.getFloat(0, 0.30f);
+            ta.recycle();
             defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
         }
 
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 2c61280..9c0900b 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.text.Editable;
 import android.text.Selection;
 import android.text.Spannable;
@@ -173,6 +174,12 @@
         return EditText.class.getName();
     }
 
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        setHandwritingArea(new Rect(0, 0, w, h));
+    }
+
     /** @hide */
     @Override
     protected boolean supportsAutoSizeText() {
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index d969a88..b2bc0764 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -57,7 +57,7 @@
  * @attr ref android.R.styleable#Gallery_gravity
  * 
  * @deprecated This widget is no longer supported. Other horizontally scrolling
- * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager}
+ * widgets include {@link HorizontalScrollView} and {@link androidx.viewpager.widget.ViewPager}
  * from the support library.
  */
 @Deprecated
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 3ad7b46..15cd17b 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -63,12 +63,12 @@
  * <p>Scroll view supports vertical scrolling only. For horizontal scrolling,
  * use {@link HorizontalScrollView} instead.</p>
  *
- * <p>Never add a {@link android.support.v7.widget.RecyclerView} or {@link ListView} to
+ * <p>Never add a {@link androidx.recyclerview.widget.RecyclerView} or {@link ListView} to
  * a scroll view. Doing so results in poor user interface performance and a poor user
  * experience.</p>
  *
  * <p class="note">
- * For vertical scrolling, consider {@link android.support.v4.widget.NestedScrollView}
+ * For vertical scrolling, consider {@link androidx.core.widget.NestedScrollView}
  * instead of scroll view which offers greater user interface flexibility and
  * support for the material design scrolling patterns.</p>
  *
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index d3600ef..872e65a 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -72,7 +72,7 @@
  * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
  * the related setSwitchTypeface() methods control that of the thumb.
  *
- * <p>{@link android.support.v7.widget.SwitchCompat} is a version of
+ * <p>{@link androidx.recyclerview.widget.RecyclerView} is a version of
  * the Switch widget which runs on devices back to API 7.</p>
  *
  * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c0c7641..3dfb4a5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5081,7 +5081,7 @@
      *
      * @param color A color value in the form 0xAARRGGBB.
      * Do not pass a resource ID. To get a color value from a resource ID, call
-     * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
+     * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}.
      *
      * @see #setTextColor(ColorStateList)
      * @see #getTextColors()
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index dc9a585..a453c28 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -373,6 +373,7 @@
             // Generate a non-activated color using the disabled alpha.
             final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
             final float disabledAlpha = ta.getFloat(0, 0.30f);
+            ta.recycle();
             defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
         }
 
diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/window/BackEvent.aidl
new file mode 100644
index 0000000..821f1fa
--- /dev/null
+++ b/core/java/android/window/BackEvent.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * @hide
+ */
+parcelable BackEvent;
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
new file mode 100644
index 0000000..14985c9
--- /dev/null
+++ b/core/java/android/window/BackEvent.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.window;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents an event that is sent out by the system during back navigation gesture.
+ * Holds information about the touch event, swipe direction and overall progress of the gesture
+ * interaction.
+ *
+ * @hide
+ */
+public class BackEvent implements Parcelable {
+    /** Indicates that the edge swipe starts from the left edge of the screen */
+    public static final int EDGE_LEFT = 0;
+    /** Indicates that the edge swipe starts from the right edge of the screen */
+    public static final int EDGE_RIGHT = 1;
+
+    @IntDef({
+            EDGE_LEFT,
+            EDGE_RIGHT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SwipeEdge{}
+
+    private final int mTouchX;
+    private final int mTouchY;
+    private final float mProgress;
+
+    @SwipeEdge
+    private final int mSwipeEdge;
+    @Nullable
+    private final RemoteAnimationTarget mDepartingAnimationTarget;
+
+    /**
+     * Creates a new {@link BackEvent} instance.
+     *
+     * @param touchX Absolute X location of the touch point.
+     * @param touchY Absolute Y location of the touch point.
+     * @param progress Value between 0 and 1 on how far along the back gesture is.
+     * @param swipeEdge Indicates which edge the swipe starts from.
+     * @param departingAnimationTarget The remote animation target of the departing application
+     *                                 window.
+     */
+    public BackEvent(int touchX, int touchY, float progress, @SwipeEdge int swipeEdge,
+            @Nullable RemoteAnimationTarget departingAnimationTarget) {
+        mTouchX = touchX;
+        mTouchY = touchY;
+        mProgress = progress;
+        mSwipeEdge = swipeEdge;
+        mDepartingAnimationTarget = departingAnimationTarget;
+    }
+
+    private BackEvent(@NonNull Parcel in) {
+        mTouchX = in.readInt();
+        mTouchY = in.readInt();
+        mProgress = in.readFloat();
+        mSwipeEdge = in.readInt();
+        mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+    }
+
+    public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
+        @Override
+        public BackEvent createFromParcel(Parcel in) {
+            return new BackEvent(in);
+        }
+
+        @Override
+        public BackEvent[] newArray(int size) {
+            return new BackEvent[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mTouchX);
+        dest.writeInt(mTouchY);
+        dest.writeFloat(mProgress);
+        dest.writeInt(mSwipeEdge);
+        dest.writeTypedObject(mDepartingAnimationTarget, flags);
+    }
+
+    /**
+     * Returns a value between 0 and 1 on how far along the back gesture is.
+     */
+    public float getProgress() {
+        return mProgress;
+    }
+
+    /**
+     * Returns the absolute X location of the touch point.
+     */
+    public int getTouchX() {
+        return mTouchX;
+    }
+
+    /**
+     * Returns the absolute Y location of the touch point.
+     */
+    public int getTouchY() {
+        return mTouchY;
+    }
+
+    /**
+     * Returns the screen edge that the swipe starts from.
+     */
+    public int getSwipeEdge() {
+        return mSwipeEdge;
+    }
+
+    /**
+     * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+     * or {@code null} if the top window should not be moved for the current type of back
+     * destination.
+     */
+    @Nullable
+    public RemoteAnimationTarget getDepartingAnimationTarget() {
+        return mDepartingAnimationTarget;
+    }
+
+    @Override
+    public String toString() {
+        return "BackEvent{"
+                + "mTouchX=" + mTouchX
+                + ", mTouchY=" + mTouchY
+                + ", mProgress=" + mProgress
+                + ", mSwipeEdge" + mSwipeEdge
+                + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+                + "}";
+    }
+}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index a42863c..47796de 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -17,6 +17,8 @@
 
 package android.window;
 
+import android.window.BackEvent;
+
 /**
  * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
  * and called from back handling process when back is invoked.
@@ -38,7 +40,7 @@
      * @param touchY Absolute Y location of the touch point.
      * @param progress Value between 0 and 1 on how far along the back gesture is.
      */
-    void onBackProgressed(int touchX, int touchY, float progress);
+    void onBackProgressed(in BackEvent backEvent);
 
     /**
      * Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index d37d3b4..03de479 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -55,10 +55,6 @@
     private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties
             .getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
 
-    /** The currently most prioritized callback. */
-    @Nullable
-    private OnBackInvokedCallbackWrapper mTopCallback;
-
     /** Convenience hashmap to quickly decide if a callback has been added. */
     private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
     /** Holds all callbacks by priorities. */
@@ -72,8 +68,8 @@
     public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) {
         mWindowSession = windowSession;
         mWindow = window;
-        if (mTopCallback != null) {
-            setTopOnBackInvokedCallback(mTopCallback);
+        if (!mAllCallbacks.isEmpty()) {
+            setTopOnBackInvokedCallback(getTopCallback());
         }
     }
 
@@ -81,6 +77,7 @@
     public void detachFromWindow() {
         mWindow = null;
         mWindowSession = null;
+        clear();
     }
 
     // TODO: Take an Executor for the callback to run on.
@@ -110,11 +107,13 @@
             mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
         }
 
+        OnBackInvokedCallback previousTopCallback = getTopCallback();
         callbacks.add(callback);
         mAllCallbacks.put(callback, priority);
-        if (mTopCallback == null || (mTopCallback.getCallback() != callback
-                && mAllCallbacks.get(mTopCallback.getCallback()) <= priority)) {
-            setTopOnBackInvokedCallback(new OnBackInvokedCallbackWrapper(callback, priority));
+        if (previousTopCallback == null
+                || (previousTopCallback != callback
+                        && mAllCallbacks.get(previousTopCallback) <= priority)) {
+            setTopOnBackInvokedCallback(callback);
         }
     }
 
@@ -126,11 +125,17 @@
             }
             return;
         }
+        OnBackInvokedCallback previousTopCallback = getTopCallback();
         Integer priority = mAllCallbacks.get(callback);
-        mOnBackInvokedCallbacks.get(priority).remove(callback);
+        ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+        callbacks.remove(callback);
+        if (callbacks.isEmpty()) {
+            mOnBackInvokedCallbacks.remove(priority);
+        }
         mAllCallbacks.remove(callback);
-        if (mTopCallback != null && mTopCallback.getCallback() == callback) {
-            findAndSetTopOnBackInvokedCallback();
+        // Re-populate the top callback to WM if the removed callback was previously the top one.
+        if (previousTopCallback == callback) {
+            setTopOnBackInvokedCallback(getTopCallback());
         }
     }
 
@@ -141,41 +146,26 @@
 
     /** Clears all registered callbacks on the instance. */
     public void clear() {
+        if (!mAllCallbacks.isEmpty()) {
+            // Clear binder references in WM.
+            setTopOnBackInvokedCallback(null);
+        }
         mAllCallbacks.clear();
-        mTopCallback = null;
         mOnBackInvokedCallbacks.clear();
     }
 
-    /**
-     * Iterates through all callbacks to find the most prioritized one and pushes it to
-     * window manager.
-     */
-    private void findAndSetTopOnBackInvokedCallback() {
-        if (mAllCallbacks.isEmpty()) {
-            setTopOnBackInvokedCallback(null);
-            return;
-        }
-
-        for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
-            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-            if (!callbacks.isEmpty()) {
-                OnBackInvokedCallbackWrapper callback = new OnBackInvokedCallbackWrapper(
-                        callbacks.get(callbacks.size() - 1), priority);
-                setTopOnBackInvokedCallback(callback);
-                return;
-            }
-        }
-        setTopOnBackInvokedCallback(null);
-    }
-
-    // Pushes the top priority callback to window manager.
-    private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallbackWrapper callback) {
-        mTopCallback = callback;
+    private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
         if (mWindowSession == null || mWindow == null) {
             return;
         }
         try {
-            mWindowSession.setOnBackInvokedCallback(mWindow, mTopCallback);
+            if (callback == null) {
+                mWindowSession.setOnBackInvokedCallback(mWindow, null);
+            } else {
+                int priority = mAllCallbacks.get(callback);
+                mWindowSession.setOnBackInvokedCallback(
+                        mWindow, new OnBackInvokedCallbackWrapper(callback, priority));
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e);
         }
@@ -202,9 +192,9 @@
         }
 
         @Override
-        public void onBackProgressed(int touchX, int touchY, float progress)
+        public void onBackProgressed(BackEvent backEvent)
                 throws RemoteException {
-            Handler.getMain().post(() -> mCallback.onBackProgressed(touchX, touchY, progress));
+            Handler.getMain().post(() -> mCallback.onBackProgressed(backEvent));
         }
 
         @Override
@@ -220,7 +210,16 @@
 
     @Override
     public OnBackInvokedCallback getTopCallback() {
-        return mTopCallback == null ? null : mTopCallback.getCallback();
+        if (mAllCallbacks.isEmpty()) {
+            return null;
+        }
+        for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
+            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+            if (!callbacks.isEmpty()) {
+                return callbacks.get(callbacks.size() - 1);
+            }
+        }
+        return null;
     }
 
     /**
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 4ae6bf7..150eb65 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -217,6 +217,12 @@
     private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
     public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
 
+    private static final String[] QUERY_FILE_INFO_PROJECTION = {
+        OpenableColumns.DISPLAY_NAME,
+        Downloads.Impl.COLUMN_TITLE,
+        DocumentsContract.Document.COLUMN_FLAGS
+    };
+
     private static final String PLURALS_COUNT = "count";
     private static final String PLURALS_FILE_NAME = "file_name";
 
@@ -1474,15 +1480,15 @@
      * and to avoid mocking Android core classes.
      */
     @VisibleForTesting
-    public Cursor queryResolver(ContentResolver resolver, Uri uri) {
-        return resolver.query(uri, null, null, null, null);
+    public Cursor queryResolver(ContentResolver resolver, String[] projection, Uri uri) {
+        return resolver.query(uri, projection, null, null, null);
     }
 
     private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
         String fileName = null;
         boolean hasThumbnail = false;
 
-        try (Cursor cursor = queryResolver(resolver, uri)) {
+        try (Cursor cursor = queryResolver(resolver, QUERY_FILE_INFO_PROJECTION, uri)) {
             if (cursor != null && cursor.getCount() > 0) {
                 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 51eb429..46f54ce 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -308,5 +308,7 @@
     /** Notifies System UI about an update to the media tap-to-transfer receiver state. */
     void updateMediaTapToTransferReceiverDisplay(
         int displayState,
-        in MediaRoute2Info routeInfo);
+        in MediaRoute2Info routeInfo,
+        in Icon appIcon,
+        in CharSequence appName);
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 0c45e5b..6c17df1 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -208,5 +208,7 @@
     /** Notifies System UI about an update to the media tap-to-transfer receiver state. */
     void updateMediaTapToTransferReceiverDisplay(
         int displayState,
-        in MediaRoute2Info routeInfo);
+        in MediaRoute2Info routeInfo,
+        in Icon appIcon,
+        in CharSequence appName);
 }
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 7a712e5..ced2722 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -627,7 +627,7 @@
     }
 
     /**
-     * Framework copy of functions needed from android.support.v4.graphics.ColorUtils.
+     * Framework copy of functions needed from androidx.core.graphics.ColorUtils.
      */
     private static class ColorUtilsFromCompat {
         private static final double XYZ_WHITE_REFERENCE_X = 95.047;
diff --git a/core/java/com/android/internal/widget/GridLayoutManager.java b/core/java/com/android/internal/widget/GridLayoutManager.java
index 09e6a99..3873e3b 100644
--- a/core/java/com/android/internal/widget/GridLayoutManager.java
+++ b/core/java/com/android/internal/widget/GridLayoutManager.java
@@ -29,7 +29,7 @@
 
 /**
  * Note: This GridLayoutManager widget may lack of latest fix because it is ported from
- * oc-dr1-release version of android.support.v7.widget.GridLayoutManager due to compatibility
+ * oc-dr1-release version of androidx.gridlayout.widget.GridLayoutManager due to compatibility
  * concern with other internal widgets, like {@link RecyclerView} and {@link LinearLayoutManager},
  * and is merely used for {@link com.android.internal.app.ChooserActivity}.
  *
diff --git a/core/java/com/android/internal/widget/PagerAdapter.java b/core/java/com/android/internal/widget/PagerAdapter.java
index 910a720..c595f5c 100644
--- a/core/java/com/android/internal/widget/PagerAdapter.java
+++ b/core/java/com/android/internal/widget/PagerAdapter.java
@@ -24,10 +24,10 @@
 
 /**
  * Base class providing the adapter to populate pages inside of
- * a {@link android.support.v4.view.ViewPager}.  You will most likely want to use a more
+ * a {@link androidx.viewpager.view.ViewPager}.  You will most likely want to use a more
  * specific implementation of this, such as
- * {@link android.support.v4.app.FragmentPagerAdapter} or
- * {@link android.support.v4.app.FragmentStatePagerAdapter}.
+ * {@link androidx.fragment.app.FragmentPagerAdapter} or
+ * {@link androidx.fragment.app.FragmentStatePagerAdapter}.
  *
  * <p>When you implement a PagerAdapter, you must override the following methods
  * at minimum:</p>
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index be15a9b..e27557a 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -1299,7 +1299,7 @@
      * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
      * This can be useful if you have multiple RecyclerViews with adapters that use the same
      * view types, for example if you have several data sets with the same kinds of item views
-     * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
+     * displayed by a {@link androidx.viewpager.view.ViewPager ViewPager}.
      *
      * @param pool Pool to set. If this parameter is null a new pool will be created and used.
      */
@@ -9764,13 +9764,13 @@
          * Some general properties that a LayoutManager may want to use.
          */
         public static class Properties {
-            /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */
+            /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation */
             public int orientation;
-            /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */
+            /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_spanCount */
             public int spanCount;
-            /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */
+            /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout */
             public boolean reverseLayout;
-            /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */
+            /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd */
             public boolean stackFromEnd;
         }
     }
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 21e63c5..4d6151d 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -115,6 +115,7 @@
                     break;
             }
         }
+        a.recycle();
 
         // Set up density-dependent properties.
         // TODO: Move these to a default style.
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 24c0d2a..2a4f812 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -67,6 +67,7 @@
 ### Graphics ###
 per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
 per-file android_hardware_HardwareBuffer.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file android_hardware_SyncFence.cpp = file:/graphics/java/android/graphics/OWNERS
 
 ### Text ###
 per-file android_text_* = file:/core/java/android/text/OWNERS
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 2bec733..dc55c05 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2476,37 +2476,47 @@
     return 4000; // SAMPLE_RATE_HZ_MIN  (for API)
 }
 
-static jint
-android_media_AudioSystem_setAssistantUid(JNIEnv *env, jobject thiz, jint uid)
-{
-    status_t status = AudioSystem::setAssistantUid(uid);
+static std::vector<uid_t> convertJIntArrayToUidVector(JNIEnv *env, jintArray jArray) {
+    std::vector<uid_t> nativeVector;
+    if (jArray != nullptr) {
+        jsize len = env->GetArrayLength(jArray);
+
+        if (len > 0) {
+            int *nativeArray = nullptr;
+            nativeArray = env->GetIntArrayElements(jArray, 0);
+            if (nativeArray != nullptr) {
+                for (size_t i = 0; i < len; i++) {
+                    nativeVector.push_back(nativeArray[i]);
+                }
+                env->ReleaseIntArrayElements(jArray, nativeArray, 0);
+            }
+        }
+    }
+    return nativeVector;
+}
+
+static jint android_media_AudioSystem_setAssistantServicesUids(JNIEnv *env, jobject thiz,
+                                                               jintArray uids) {
+    std::vector<uid_t> nativeUidsVector = convertJIntArrayToUidVector(env, uids);
+
+    status_t status = AudioSystem::setAssistantServicesUids(nativeUidsVector);
+
     return (jint)nativeToJavaStatus(status);
 }
 
-static jint android_media_AudioSystem_setHotwordDetectionServiceUid(JNIEnv *env, jobject thiz,
-                                                                    jint uid) {
-    status_t status = AudioSystem::setHotwordDetectionServiceUid(uid);
+static jint android_media_AudioSystem_setActiveAssistantServicesUids(JNIEnv *env, jobject thiz,
+                                                                     jintArray activeUids) {
+    std::vector<uid_t> nativeActiveUidsVector = convertJIntArrayToUidVector(env, activeUids);
+
+    status_t status = AudioSystem::setActiveAssistantServicesUids(nativeActiveUidsVector);
+
     return (jint)nativeToJavaStatus(status);
 }
 
 static jint
 android_media_AudioSystem_setA11yServicesUids(JNIEnv *env, jobject thiz, jintArray uids) {
-    std::vector<uid_t> nativeUidsVector;
+    std::vector<uid_t> nativeUidsVector = convertJIntArrayToUidVector(env, uids);
 
-    if (uids != nullptr) {
-       jsize len = env->GetArrayLength(uids);
-
-       if (len > 0) {
-           int *nativeUids = nullptr;
-           nativeUids = env->GetIntArrayElements(uids, 0);
-           if (nativeUids != nullptr) {
-               for (size_t i = 0; i < len; i++) {
-                   nativeUidsVector.push_back(nativeUids[i]);
-               }
-               env->ReleaseIntArrayElements(uids, nativeUids, 0);
-           }
-       }
-    }
     status_t status = AudioSystem::setA11yServicesUids(nativeUidsVector);
     return (jint)nativeToJavaStatus(status);
 }
@@ -3000,9 +3010,10 @@
           (void *)android_media_AudioSystem_getReportedSurroundFormats},
          {"setSurroundFormatEnabled", "(IZ)I",
           (void *)android_media_AudioSystem_setSurroundFormatEnabled},
-         {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid},
-         {"setHotwordDetectionServiceUid", "(I)I",
-          (void *)android_media_AudioSystem_setHotwordDetectionServiceUid},
+         {"setAssistantServicesUids", "([I)I",
+          (void *)android_media_AudioSystem_setAssistantServicesUids},
+         {"setActiveAssistantServicesUids", "([I)I",
+          (void *)android_media_AudioSystem_setActiveAssistantServicesUids},
          {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
          {"isHapticPlaybackSupported", "()Z",
           (void *)android_media_AudioSystem_isHapticPlaybackSupported},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 2488b57..336161c 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -245,6 +245,13 @@
     jmethodID onTransactionCommitted;
 } gTransactionCommittedListenerClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+    jfieldID format;
+    jfieldID alphaInterpretation;
+} gDisplayDecorationSupportInfo;
+
 class JNamedColorSpace {
 public:
     // ColorSpace.Named.SRGB.ordinal() = 0;
@@ -1792,13 +1799,29 @@
     client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius);
 }
 
-static jboolean nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz,
-        jobject displayTokenObject) {
+static jobject nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz,
+                                                 jobject displayTokenObject) {
     sp<IBinder> displayToken(ibinderForJavaObject(env, displayTokenObject));
     if (displayToken == nullptr) {
-        return JNI_FALSE;
+        return nullptr;
     }
-    return static_cast<jboolean>(SurfaceComposerClient::getDisplayDecorationSupport(displayToken));
+    const auto support = SurfaceComposerClient::getDisplayDecorationSupport(displayToken);
+    if (!support) {
+        return nullptr;
+    }
+
+    jobject jDisplayDecorationSupport =
+            env->NewObject(gDisplayDecorationSupportInfo.clazz, gDisplayDecorationSupportInfo.ctor);
+    if (jDisplayDecorationSupport == nullptr) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+        return nullptr;
+    }
+
+    env->SetIntField(jDisplayDecorationSupport, gDisplayDecorationSupportInfo.format,
+                     static_cast<jint>(support.value().format));
+    env->SetIntField(jDisplayDecorationSupport, gDisplayDecorationSupportInfo.alphaInterpretation,
+                     static_cast<jint>(support.value().alphaInterpretation));
+    return jDisplayDecorationSupport;
 }
 
 static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
@@ -2131,7 +2154,8 @@
             (void*)nativeMirrorSurface },
     {"nativeSetGlobalShadowSettings", "([F[FFFF)V",
             (void*)nativeSetGlobalShadowSettings },
-    {"nativeGetDisplayDecorationSupport", "(Landroid/os/IBinder;)Z",
+    {"nativeGetDisplayDecorationSupport",
+            "(Landroid/os/IBinder;)Landroid/hardware/graphics/common/DisplayDecorationSupport;",
             (void*)nativeGetDisplayDecorationSupport},
     {"nativeGetHandle", "(J)J",
             (void*)nativeGetHandle },
@@ -2390,6 +2414,17 @@
     gTransactionCommittedListenerClassInfo.onTransactionCommitted =
             GetMethodIDOrDie(env, transactionCommittedListenerClazz, "onTransactionCommitted",
                              "()V");
+
+    jclass displayDecorationSupportClazz =
+            FindClassOrDie(env, "android/hardware/graphics/common/DisplayDecorationSupport");
+    gDisplayDecorationSupportInfo.clazz = MakeGlobalRefOrDie(env, displayDecorationSupportClazz);
+    gDisplayDecorationSupportInfo.ctor =
+            GetMethodIDOrDie(env, displayDecorationSupportClazz, "<init>", "()V");
+    gDisplayDecorationSupportInfo.format =
+            GetFieldIDOrDie(env, displayDecorationSupportClazz, "format", "I");
+    gDisplayDecorationSupportInfo.alphaInterpretation =
+            GetFieldIDOrDie(env, displayDecorationSupportClazz, "alphaInterpretation", "I");
+
     return err;
 }
 
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index 5c6116a..279a5d0 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -115,4 +115,5 @@
     }
     optional Detail detail = 17;
     repeated string overlay_paths = 18;
+    repeated string known_activity_embedding_certs = 19;
 }
diff --git a/core/proto/android/internal/binder_latency.proto b/core/proto/android/internal/binder_latency.proto
index 8b11f5b..edd9b71 100644
--- a/core/proto/android/internal/binder_latency.proto
+++ b/core/proto/android/internal/binder_latency.proto
@@ -35,6 +35,7 @@
     SYSTEM_SERVER = 1;
     TELEPHONY = 2;
     BLUETOOTH = 3;
+    WIFI = 4;
   }
 
   enum ServiceClassName {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f29de56..0c41f31 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1905,6 +1905,13 @@
     <permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows applications to get notified when a Wi-Fi interface request cannot
+         be satisfied without tearing down one or more other interfaces, and provide a decision
+         whether to approve the request or reject it.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_WIFI_INTERFACES"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows apps to create and manage IPsec tunnels.
          <p>Only granted to applications that are currently bound by the
          system for creating and managing IPsec-based interfaces.
@@ -4403,6 +4410,16 @@
     <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
         android:protectionLevel="normal|appop"/>
 
+    <!-- Allows apps to use exact alarms just like with SCHEDULE_EXACT_ALARM but without needing
+        to request this permission from the user.
+        <p><b>This is only for apps that rely on exact alarms for their core functionality.</b>
+        App stores may enforce policies to audit and review the use of this permission. Any app that
+        requests this but is found to not require exact alarms for its primary function may be
+        removed from the app store.
+    -->
+    <permission android:name="android.permission.USE_EXACT_ALARM"
+                android:protectionLevel="normal"/>
+
     <!-- Allows an application to query tablet mode state and monitor changes
          in it.
          <p>Not for use by third-party applications.
@@ -6619,6 +6636,14 @@
                   android:exported="false">
         </activity>
 
+        <activity android:name="com.android.server.logcat.LogAccessConfirmationActivity"
+                  android:theme="@style/Theme.Dialog.Confirmation"
+                  android:excludeFromRecents="true"
+                  android:process=":ui"
+                  android:label="@string/log_access_confirmation_title"
+                  android:exported="false">
+        </activity>
+
         <activity android:name="com.android.server.notification.NASLearnMoreActivity"
                   android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 090b1c52..3a9cb2e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2332,6 +2332,17 @@
              contrast between the window background and the icon. Note the shape would also be
              masking like an icon. -->
         <attr name="windowSplashScreenIconBackgroundColor" format="color"/>
+
+        <!-- Specify whether this application always wants the icon to be displayed on the splash
+             screen. -->
+        <attr name="windowSplashScreenBehavior">
+            <!-- The icon is shown when the launching activity sets the splashScreenStyle to
+                 SPLASH_SCREEN_STYLE_ICON. If the launching activity does not specify any style,
+                 follow the system behavior. -->
+            <enum name="default" value="0" />
+            <!-- The icon is shown unless the launching app specified SPLASH_SCREEN_STYLE_EMPTY -->
+            <enum name="icon_preferred" value="1" />
+        </attr>
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index cb40e86..6dc975b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1048,6 +1048,24 @@
          <p>The default value of this attribute is <code>false</code>. -->
     <attr name="allowEmbedded" format="boolean" />
 
+    <!-- A reference to an array resource containing the signing certificate digests, one of which a
+         client is required to be signed with in order to embed the activity. If the client is not
+         signed with one of the certificates in the set, and the activity does not allow embedding
+         by untrusted hosts via {@link android.R.attr#allowUntrustedActivityEmbedding} flag, the
+         embedding request will fail.
+         <p>The digest should be computed over the DER encoding of the trusted certificate using the
+         SHA-256 digest algorithm.
+         <p>If only a single signer is declared this can also be a string resource, or the digest
+         can be declared inline as the value for this attribute.
+         <p>If the attribute is declared both on the application and the activity level, the value
+         on the activity level takes precedence. -->
+    <attr name="knownActivityEmbeddingCerts" format="reference|string" />
+
+    <!-- Indicate that the activity can be embedded by untrusted hosts. In this case the
+         interactions and visibility of the embedded activity may be limited.
+         <p>The default value of this attribute is <code>false</code>. -->
+    <attr name="allowUntrustedActivityEmbedding" format="boolean" />
+
     <!-- Specifies whether this {@link android.app.Activity} should be shown on
          top of the lock screen whenever the lockscreen is up and this activity has another
          activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
@@ -2011,6 +2029,7 @@
              when the application's user data is cleared. The default value is false.
         -->
         <attr name="resetEnabledSettingsOnAppDataCleared" format="boolean" />
+        <attr name="knownActivityEmbeddingCerts" />
     </declare-styleable>
 
     <!-- An attribution is a logical part of an app and is identified by a tag.
@@ -3033,6 +3052,8 @@
         <!-- Indicates whether the activity can be displayed on a remote device which may or
              may not be running Android. -->
         <attr name="canDisplayOnRemoteDevices" format="boolean"/>
+        <attr name="allowUntrustedActivityEmbedding" />
+        <attr name="knownActivityEmbeddingCerts" />
     </declare-styleable>
 
     <!-- The <code>activity-alias</code> tag declares a new
@@ -3073,6 +3094,8 @@
         <attr name="exported" />
         <attr name="parentActivityName" />
         <attr name="attributionTags" />
+        <attr name="allowUntrustedActivityEmbedding" />
+        <attr name="knownActivityEmbeddingCerts" />
     </declare-styleable>
 
     <!-- The <code>meta-data</code> tag is used to attach additional
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 775527d..6eb19e1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4276,6 +4276,9 @@
     <!-- URI for in call notification sound -->
     <string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string>
 
+    <!-- URI for camera shutter sound -->
+    <string translatable="false" name="config_cameraShutterSound">/product/media/audio/ui/camera_click.ogg</string>
+
     <!-- URI for default ringtone sound file to be used for silent ringer vibration -->
     <string translatable="false" name="config_defaultRingtoneVibrationSound"></string>
 
@@ -5697,4 +5700,75 @@
      -->
     <string-array name="config_dockExtconStateMapping">
     </string-array>
+
+    <!-- Whether or not the monitoring on the apps' background battery drain is enabled -->
+    <bool name="config_bg_current_drain_monitor_enabled">true</bool>
+
+    <!-- The threshold of the background current drain (in percentage) to the restricted
+         standby bucket.
+    -->
+    <array name="config_bg_current_drain_threshold_to_restricted_bucket">
+        <item>2.0</item> <!-- regular device -->
+        <item>4.0</item> <!-- low ram device -->
+    </array>
+
+    <!-- The threshold of the background current drain (in percentage) to the background
+         restricted level.
+    -->
+    <array name="config_bg_current_drain_threshold_to_bg_restricted">
+        <item>4.0</item> <!-- regular device -->
+        <item>8.0</item> <!-- low ram device -->
+    </array>
+
+    <!-- The background current drain monitoring window size. -->
+    <integer name="config_bg_current_drain_window">86400</integer>
+
+    <!-- The types of battery drain we're checking on each app; if the sum of the battery drain
+        exceeds the threshold, it'll be moved to restricted standby bucket. The value must be
+        one of or combination of the definitions in AppBatteryPolicy.
+    -->
+    <integer name="config_bg_current_drain_types_to_restricted_bucket">4</integer>
+
+    <!-- The types of battery drain we're checking on each app; if the sum of the battery drain
+        exceeds the threshold, it'll be moved to background restricted level. The value must be
+        one of or combination of the definitions in AppBatteryPolicy.
+    -->
+    <integer name="config_bg_current_drain_types_to_bg_restricted">12</integer>
+
+    <!-- The power usage components we're monitoring. Must one of the definition in BatteryConsumer.
+    -->
+    <integer name="config_bg_current_drain_power_components">-1</integer>
+
+    <!-- Whether or not enable the different threshold based on the durations of
+         certain event type.
+    -->
+    <bool name="config_bg_current_drain_event_duration_based_threshold_enabled">false</bool>
+
+    <!-- The threshold of the background current drain (in percentage) to the restricted
+         standby bucket for legitimate case with higher background current drain.
+    -->
+    <array name="config_bg_current_drain_high_threshold_to_restricted_bucket">
+        <item>30.0</item> <!-- regular device -->
+        <item>60.0</item> <!-- low ram device -->
+    </array>
+
+    <!-- The threshold of the background current drain (in percentage) to the background
+         restricted level for legitimate case with higher background current drain.
+    -->
+    <array name="config_bg_current_drain_high_threshold_to_bg_restricted">
+        <item>20.0</item> <!-- regular device -->
+        <item>40.0</item> <!-- low ram device -->
+    </array>
+
+    <!-- The threshold of minimal time of hosting a foreground service with type "mediaPlayback"
+         or a media session, over the given window, so it'd subject towards the higher background
+         current drain threshold.
+    -->
+    <integer name="config_bg_current_drain_media_playback_min_duration">1800</integer>
+
+    <!-- The threshold of minimal time of hosting a foreground service with type "location"
+         over the given window, so it'd subject towards the higher background
+         current drain threshold.
+    -->
+    <integer name="config_bg_current_drain_location_min_duration">1800</integer>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f05137e..3d80ca8 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3275,6 +3275,9 @@
     <public name="toExtendRight" />
     <public name="toExtendBottom" />
     <public name="tileService" />
+    <public name="windowSplashScreenBehavior" />
+    <public name="allowUntrustedActivityEmbedding" />
+    <public name="knownActivityEmbeddingCerts" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6297ed9..e41aa45 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5719,6 +5719,20 @@
     <!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] -->
     <string name="harmful_app_warning_title">Harmful app detected</string>
 
+    <!-- Title for the log access confirmation dialog. [CHAR LIMIT=40] -->
+    <string name="log_access_confirmation_title">System log access request</string>
+    <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+    <string name="log_access_confirmation_allow">Only this time</string>
+    <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+    <string name="log_access_confirmation_deny">Don\u2019t allow</string>
+
+    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+    <string name="log_access_confirmation_body"><xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> requests system logs for functional debugging.
+        These logs might contain information that apps and services on your device have written.</string>
+
+    <!-- Privacy notice do not show [CHAR LIMIT=20] -->
+    <string name="log_access_do_not_show_again">Don\u2019t show again</string>
+
     <!-- Text describing a permission request for one app to show another app's
          slices [CHAR LIMIT=NONE] -->
     <string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string>
@@ -6266,4 +6280,8 @@
     </string>
     <!-- Action label of notification for user to check background apps. [CHAR LIMIT=NONE]  -->
     <string name="notification_action_check_bg_apps">Check active apps</string>
+
+    <!-- Strings for VirtualDeviceManager -->
+    <!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] -->
+    <string name="vdm_camera_access_denied">Cannot access camera from this device</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d731180..c63e543 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3749,6 +3749,7 @@
   <java-symbol type="bool" name="config_handleVolumeAliasesUsingVolumeGroups" />
   <java-symbol type="dimen" name="config_inCallNotificationVolume" />
   <java-symbol type="string" name="config_inCallNotificationSound" />
+  <java-symbol type="string" name="config_cameraShutterSound" />
   <java-symbol type="integer" name="config_autoGroupAtCount" />
   <java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" />
   <java-symbol type="bool" name="config_dozeAlwaysOnEnabled" />
@@ -3868,6 +3869,11 @@
   <java-symbol type="string" name="harmful_app_warning_title" />
   <java-symbol type="layout" name="harmful_app_warning_dialog" />
 
+  <java-symbol type="string" name="log_access_confirmation_allow" />
+  <java-symbol type="string" name="log_access_confirmation_deny" />
+  <java-symbol type="string" name="log_access_confirmation_title" />
+  <java-symbol type="string" name="log_access_confirmation_body" />
+
   <java-symbol type="string" name="config_defaultAssistantAccessComponent" />
 
   <java-symbol type="string" name="slices_permission_request" />
@@ -4724,5 +4730,21 @@
   <java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" />
   <java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" />
 
+  <!-- For VirtualDeviceManager -->
+  <java-symbol type="string" name="vdm_camera_access_denied" />
+
   <java-symbol type="color" name="camera_privacy_light"/>
+
+  <java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" />
+  <java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" />
+  <java-symbol type="array" name="config_bg_current_drain_threshold_to_bg_restricted" />
+  <java-symbol type="integer" name="config_bg_current_drain_window" />
+  <java-symbol type="integer" name="config_bg_current_drain_types_to_restricted_bucket" />
+  <java-symbol type="integer" name="config_bg_current_drain_types_to_bg_restricted" />
+  <java-symbol type="integer" name="config_bg_current_drain_power_components" />
+  <java-symbol type="bool" name="config_bg_current_drain_event_duration_based_threshold_enabled" />
+  <java-symbol type="array" name="config_bg_current_drain_high_threshold_to_restricted_bucket" />
+  <java-symbol type="array" name="config_bg_current_drain_high_threshold_to_bg_restricted" />
+  <java-symbol type="integer" name="config_bg_current_drain_media_playback_min_duration" />
+  <java-symbol type="integer" name="config_bg_current_drain_location_min_duration" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java
new file mode 100644
index 0000000..c1b6666
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.activity;
+
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
+import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+
+import android.app.Activity;
+import android.app.WindowConfiguration;
+import android.app.activity.ActivityThreadTest.TestActivity;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentCallbacks;
+import android.content.TestComponentCallbacks2;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for verifying {@link Activity#registerComponentCallbacks(ComponentCallbacks)} behavior.
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:android.app.activity.RegisterComponentCallbacksTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class RegisterComponentCallbacksTest {
+    @Rule
+    public ActivityScenarioRule rule = new ActivityScenarioRule<>(TestActivity.class);
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @Test
+    public void testRegisterComponentCallbacks() {
+        final ActivityScenario scenario = rule.getScenario();
+        final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+        final Configuration config = new Configuration();
+        config.fontScale = 1.2f;
+        config.windowConfiguration.setWindowingMode(
+                WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+        final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW;
+
+        scenario.onActivity(activity -> {
+            // It should be no-op to unregister a ComponentCallbacks without registration.
+            activity.unregisterComponentCallbacks(callbacks);
+
+            activity.registerComponentCallbacks(callbacks);
+            // Verify #onConfigurationChanged
+            activity.onConfigurationChanged(config);
+            assertThat(callbacks.mConfiguration).isEqualTo(config);
+            // Verify #onTrimMemory
+            activity.onTrimMemory(trimMemoryLevel);
+            assertThat(callbacks.mLevel).isEqualTo(trimMemoryLevel);
+            // verify #onLowMemory
+            activity.onLowMemory();
+            assertThat(callbacks.mLowMemoryCalled).isTrue();
+
+            activity.unregisterComponentCallbacks(callbacks);
+        });
+    }
+
+    @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS)
+    @Test
+    public void testRegisterComponentCallbacksBeforeT() {
+        final ActivityScenario scenario = rule.getScenario();
+        final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+        final Configuration config = new Configuration();
+        config.fontScale = 1.2f;
+        config.windowConfiguration.setWindowingMode(
+                WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+        final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW;
+
+        scenario.onActivity(activity -> {
+            // It should be no-op to unregister a ComponentCallbacks without registration.
+            activity.unregisterComponentCallbacks(callbacks);
+
+            activity.registerComponentCallbacks(callbacks);
+            // Verify #onConfigurationChanged
+            activity.onConfigurationChanged(config);
+            assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
+                    + "before T.").that(callbacks.mConfiguration).isNull();
+            // Verify #onTrimMemory
+            activity.onTrimMemory(trimMemoryLevel);
+            assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
+                    + "before T.").that(callbacks.mLevel).isEqualTo(0);
+            // verify #onLowMemory
+            activity.onLowMemory();
+            assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
+                    + "before T.").that(callbacks.mLowMemoryCalled).isFalse();
+
+            activity.unregisterComponentCallbacks(callbacks);
+        });
+    }
+}
diff --git a/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java b/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java
new file mode 100644
index 0000000..d66cb71
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.companion.virtual.audio;
+
+import static android.media.AudioFormat.CHANNEL_IN_MONO;
+import static android.media.AudioFormat.CHANNEL_OUT_MONO;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.media.AudioFormat;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+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;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class VirtualAudioSessionTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Mock
+    private AudioConfigurationChangeCallback mCallback;
+    private static final int APP_UID = 100;
+    private static final int APP_UID2 = 200;
+    private static final AudioFormat AUDIO_CAPTURE_FORMAT =
+            new AudioFormat.Builder()
+                    .setSampleRate(48000)
+                    .setEncoding(ENCODING_PCM_16BIT)
+                    .setChannelMask(CHANNEL_IN_MONO)
+                    .build();
+    private static final AudioFormat AUDIO_INJECT_FORMAT =
+            new AudioFormat.Builder()
+                    .setSampleRate(48000)
+                    .setEncoding(ENCODING_PCM_16BIT)
+                    .setChannelMask(CHANNEL_OUT_MONO)
+                    .build();
+    private Context mContext;
+    private VirtualAudioSession mVirtualAudioSession;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        mVirtualAudioSession = new VirtualAudioSession(
+                mContext, mCallback, /* executor= */ null);
+    }
+
+    @Test
+    public void startAudioCapture_isSuccessful() {
+        AudioCapture audioCapture = mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT);
+
+        assertThat(audioCapture).isNotNull();
+        assertThat(mVirtualAudioSession.getAudioCapture()).isEqualTo(audioCapture);
+    }
+
+    @Test
+    public void startAudioCapture_audioCaptureAlreadyStarted_throws() {
+        mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT);
+
+        assertThrows(IllegalStateException.class,
+                () -> mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT));
+    }
+
+    @Test
+    public void startAudioInjection_isSuccessful() {
+        AudioInjection audioInjection = mVirtualAudioSession.startAudioInjection(
+                AUDIO_INJECT_FORMAT);
+
+        assertThat(audioInjection).isNotNull();
+        assertThat(mVirtualAudioSession.getAudioInjection()).isEqualTo(audioInjection);
+    }
+
+    @Test
+    public void startAudioInjection_audioInjectionAlreadyStarted_throws() {
+        mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT);
+
+        assertThrows(IllegalStateException.class,
+                () -> mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT));
+    }
+
+    @Test
+    public void onAppsNeedingAudioRoutingChanged_neverStartAudioCaptureOrInjection_throws() {
+        int[] uids = new int[]{APP_UID};
+
+        assertThrows(IllegalStateException.class,
+                () -> mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(uids));
+    }
+
+    @Test
+    public void onAppsNeedingAudioRoutingChanged_cachesReroutedApps() {
+        mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT);
+        mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT);
+        int[] appUids = new int[]{APP_UID};
+
+        mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(appUids);
+
+        assertThat(Arrays.equals(mVirtualAudioSession.getReroutedAppUids().toArray(),
+                appUids)).isTrue();
+    }
+
+    @Test
+    public void onAppsNeedingAudioRoutingChanged_receiveManyTimes_reroutedAppsSizeIsCorrect() {
+        mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT);
+        mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT);
+        int[] appUids = new int[]{APP_UID, APP_UID2};
+
+        mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(new int[]{1234});
+        mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(new int[]{5678});
+        mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(appUids);
+
+        assertThat(Arrays.equals(mVirtualAudioSession.getReroutedAppUids().toArray(),
+                appUids)).isTrue();
+        assertThat(mVirtualAudioSession.getReroutedAppUids().size()).isEqualTo(2);
+    }
+
+    @Test
+    public void close_releasesCaptureAndInjection() {
+        mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT);
+        mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT);
+
+        mVirtualAudioSession.close();
+
+        assertThat(mVirtualAudioSession.getAudioCapture()).isNull();
+        assertThat(mVirtualAudioSession.getAudioInjection()).isNull();
+    }
+
+    @Test
+    public void onPlaybackConfigChanged_sendsCallback() {
+        List<AudioPlaybackConfiguration> configs = new ArrayList<>();
+
+        mVirtualAudioSession.onPlaybackConfigChanged(configs);
+
+        verify(mCallback).onPlaybackConfigChanged(configs);
+    }
+
+    @Test
+    public void onRecordingConfigChanged_sendCallback() {
+        List<AudioRecordingConfiguration> configs = new ArrayList<>();
+
+        mVirtualAudioSession.onRecordingConfigChanged(configs);
+
+        verify(mCallback).onRecordingConfigChanged(configs);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/ContextWrapperTest.java b/core/tests/coretests/src/android/content/ContextWrapperTest.java
index ecaf1f4..4957702 100644
--- a/core/tests/coretests/src/android/content/ContextWrapperTest.java
+++ b/core/tests/coretests/src/android/content/ContextWrapperTest.java
@@ -16,7 +16,7 @@
 
 package android.content;
 
-import static android.content.ContextWrapper.COMPONENT_CALLBACK_ON_WRAPPER;
+import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
@@ -61,7 +61,7 @@
      * register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before
      * {@link ContextWrapper#attachBaseContext(Context)}.
      */
-    @DisableCompatChanges(COMPONENT_CALLBACK_ON_WRAPPER)
+    @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS)
     @Test
     public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() {
         final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index 0b94589..a69c6ff 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -1,6 +1,7 @@
 per-file AssetTest.java = file:/core/java/android/content/res/OWNERS
-per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file Context* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file Context* = [email protected]
 per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
 per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
-per-file ComponentCallbacksControllerTest = file:/services/core/java/com/android/server/wm/OWNERS
-per-file ComponentCallbacksControllerTest = [email protected]
+per-file *ComponentCallbacks* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *ComponentCallbacks* = [email protected]
diff --git a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
index 6ae7fc4..5c8787a 100644
--- a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
+++ b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
@@ -20,10 +20,10 @@
 
 import androidx.annotation.NonNull;
 
-class TestComponentCallbacks2 implements ComponentCallbacks2 {
-    android.content.res.Configuration mConfiguration;
-    boolean mLowMemoryCalled;
-    int mLevel;
+public class TestComponentCallbacks2 implements ComponentCallbacks2 {
+    public Configuration mConfiguration = null;
+    public boolean mLowMemoryCalled = false;
+    public int mLevel = 0;
 
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
diff --git a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
index 7af96c3..36072c3 100644
--- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
@@ -104,6 +104,17 @@
     }
 
     @Test
+    public void testValidAtomicFormula_stringValue_appCertificateLineageIsNotAutoHashed() {
+        String appCert = "cert";
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE, appCert);
+
+        assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE_LINEAGE);
+        assertThat(stringAtomicFormula.getValue()).matches(appCert);
+        assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
+    }
+
+    @Test
     public void testValidAtomicFormula_stringValue_installerCertificateIsNotAutoHashed() {
         String installerCert = "cert";
         StringAtomicFormula stringAtomicFormula =
@@ -285,6 +296,34 @@
     }
 
     @Test
+    public void testFormulaMatches_string_multipleAppCertificateLineage_true() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(
+                        AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */ true);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder()
+                        .setPackageName("com.test.app")
+                        .setAppCertificateLineage(Arrays.asList("test-cert", "cert"))
+                        .build();
+
+        assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue();
+    }
+
+    @Test
+    public void testFormulaMatches_string_multipleAppCertificateLineage_false() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(
+                        AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */ true);
+        AppInstallMetadata appInstallMetadata =
+                getAppInstallMetadataBuilder()
+                        .setPackageName("com.test.app")
+                        .setAppCertificateLineage(Arrays.asList("test-cert", "another-cert"))
+                        .build();
+
+        assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse();
+    }
+
+    @Test
     public void testFormulaMatches_string_multipleInstallerCertificates_true() {
         StringAtomicFormula stringAtomicFormula =
                 new StringAtomicFormula(
@@ -324,6 +363,15 @@
     }
 
     @Test
+    public void testIsAppCertificateLineageFormula_string_true() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(
+                        AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */false);
+
+        assertThat(stringAtomicFormula.isAppCertificateLineageFormula()).isTrue();
+    }
+
+    @Test
     public void testIsAppCertificateFormula_string_false() {
         StringAtomicFormula stringAtomicFormula =
                 new StringAtomicFormula(
@@ -334,6 +382,16 @@
     }
 
     @Test
+    public void testIsAppCertificateLineageFormula_string_false() {
+        StringAtomicFormula stringAtomicFormula =
+                new StringAtomicFormula(
+                        AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */
+                        false);
+
+        assertThat(stringAtomicFormula.isAppCertificateLineageFormula()).isFalse();
+    }
+
+    @Test
     public void testIsInstallerFormula_string_false() {
         StringAtomicFormula stringAtomicFormula =
                 new StringAtomicFormula(
@@ -442,6 +500,15 @@
     }
 
     @Test
+    public void testIsAppCertificateLineageFormula_long_false() {
+        LongAtomicFormula longAtomicFormula =
+                new AtomicFormula.LongAtomicFormula(
+                        AtomicFormula.VERSION_CODE, AtomicFormula.GTE, 1);
+
+        assertThat(longAtomicFormula.isAppCertificateLineageFormula()).isFalse();
+    }
+
+    @Test
     public void testIsInstallerFormula_long_false() {
         LongAtomicFormula longAtomicFormula =
                 new LongAtomicFormula(
@@ -479,6 +546,14 @@
     }
 
     @Test
+    public void testIsAppCertificateLineageFormula_bool_false() {
+        BooleanAtomicFormula boolFormula =
+                new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
+
+        assertThat(boolFormula.isAppCertificateLineageFormula()).isFalse();
+    }
+
+    @Test
     public void testIsInstallerFormula_bool_false() {
         BooleanAtomicFormula boolFormula =
                 new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
@@ -491,6 +566,7 @@
         return new AppInstallMetadata.Builder()
                 .setPackageName("abc")
                 .setAppCertificates(Collections.singletonList("abc"))
+                .setAppCertificateLineage(Collections.singletonList("abc"))
                 .setInstallerCertificates(Collections.singletonList("abc"))
                 .setInstallerName("abc")
                 .setVersionCode(-1)
diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
index 593e70e..a202efb 100644
--- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
@@ -249,6 +249,28 @@
     }
 
     @Test
+    public void testIsAppCertificateLineageFormula_false() {
+        CompoundFormula compoundFormula =
+                new CompoundFormula(
+                        CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
+
+        assertThat(compoundFormula.isAppCertificateLineageFormula()).isFalse();
+    }
+
+    @Test
+    public void testIsAppCertificateLineageFormula_true() {
+        AtomicFormula appCertFormula =
+                new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE,
+                        "app.cert", /* isHashed= */false);
+        CompoundFormula compoundFormula =
+                new CompoundFormula(
+                        CompoundFormula.AND,
+                        Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2, appCertFormula));
+
+        assertThat(compoundFormula.isAppCertificateLineageFormula()).isTrue();
+    }
+
+    @Test
     public void testIsInstallerFormula_false() {
         CompoundFormula compoundFormula =
                 new CompoundFormula(
@@ -288,6 +310,7 @@
         return new AppInstallMetadata.Builder()
                 .setPackageName("abc")
                 .setAppCertificates(Collections.singletonList("abc"))
+                .setAppCertificateLineage(Collections.singletonList("abc"))
                 .setInstallerCertificates(Collections.singletonList("abc"))
                 .setInstallerName("abc")
                 .setVersionCode(-1)
diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
index 70712e4..54acb1e 100644
--- a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
@@ -32,8 +32,8 @@
 @RunWith(JUnit4.class)
 public class InstallerAllowedByManifestFormulaTest {
 
-    private static final InstallerAllowedByManifestFormula
-            FORMULA = new InstallerAllowedByManifestFormula();
+    private static final InstallerAllowedByManifestFormula FORMULA =
+            new InstallerAllowedByManifestFormula();
 
     @Test
     public void testFormulaMatches_installerAndCertBothInManifest() {
@@ -115,6 +115,7 @@
         return new AppInstallMetadata.Builder()
                 .setPackageName("abc")
                 .setAppCertificates(Collections.emptyList())
+                .setAppCertificateLineage(Collections.emptyList())
                 .setInstallerCertificates(Collections.emptyList())
                 .setInstallerName("abc")
                 .setVersionCode(-1)
diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
index 7e4c138..9058a71 100644
--- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
@@ -54,6 +54,20 @@
     }
 
     @Test
+    public void createEqualsFormula_appCertificateLineage() {
+        String appCertificate = "com.test.app";
+        IntegrityFormula formula =
+                IntegrityFormula.Application.certificateLineageContains(appCertificate);
+
+        AtomicFormula.StringAtomicFormula stringAtomicFormula =
+                (AtomicFormula.StringAtomicFormula) formula;
+
+        assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE_LINEAGE);
+        assertThat(stringAtomicFormula.getValue()).matches(appCertificate);
+        assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
+    }
+
+    @Test
     public void createEqualsFormula_installerName() {
         String installerName = "com.test.app";
         IntegrityFormula formula = IntegrityFormula.Installer.packageNameEquals(installerName);
@@ -138,8 +152,10 @@
         IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName);
         IntegrityFormula formula2 =
                 IntegrityFormula.Application.certificatesContain(certificateName);
+        IntegrityFormula formula3 =
+                IntegrityFormula.Application.certificateLineageContains(certificateName);
 
-        IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2);
+        IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2, formula3);
 
         assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG);
     }
@@ -151,8 +167,10 @@
         IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName);
         IntegrityFormula formula2 =
                 IntegrityFormula.Application.certificatesContain(certificateName);
+        IntegrityFormula formula3 =
+                IntegrityFormula.Application.certificateLineageContains(certificateName);
 
-        IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2);
+        IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2, formula3);
 
         assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG);
     }
diff --git a/core/tests/coretests/src/android/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java
index 5dbe03e..826eb30 100644
--- a/core/tests/coretests/src/android/util/RotationUtilsTest.java
+++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java
@@ -17,12 +17,14 @@
 package android.util;
 
 import static android.util.RotationUtils.rotateBounds;
+import static android.util.RotationUtils.rotatePoint;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
 import static org.junit.Assert.assertEquals;
 
+import android.graphics.Point;
 import android.graphics.Rect;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -58,4 +60,23 @@
         rotateBounds(testResult, testParent, ROTATION_270);
         assertEquals(new Rect(520, 40, 580, 120), testResult);
     }
+
+    @Test
+    public void testRotatePoint() {
+        int parentW = 1000;
+        int parentH = 600;
+        Point testPt = new Point(60, 40);
+
+        Point testResult = new Point(testPt);
+        rotatePoint(testResult, ROTATION_90, parentW, parentH);
+        assertEquals(new Point(40, 940), testResult);
+
+        testResult.set(testPt.x, testPt.y);
+        rotatePoint(testResult, ROTATION_180, parentW, parentH);
+        assertEquals(new Point(940, 560), testResult);
+
+        testResult.set(testPt.x, testPt.y);
+        rotatePoint(testResult, ROTATION_270, parentW, parentH);
+        assertEquals(new Point(560, 60), testResult);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java b/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java
new file mode 100644
index 0000000..4bea54b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.stylus;
+
+import static android.view.stylus.HandwritingTestUtil.createView;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingInitiator;
+import android.view.View;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HandwritableViewInfoTest {
+
+    @Test
+    public void constructorTest() {
+        final Rect rect = new Rect(1, 2, 3, 4);
+        final View view = createView(rect);
+        final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo =
+                new HandwritingInitiator.HandwritableViewInfo(view);
+
+        assertThat(handwritableViewInfo.getView()).isEqualTo(view);
+        // It's labeled dirty by default.
+        assertTrue(handwritableViewInfo.mIsDirty);
+    }
+
+    @Test
+    public void update() {
+        final Rect rect = new Rect(1, 2, 3, 4);
+        final View view = createView(rect);
+        final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo =
+                new HandwritingInitiator.HandwritableViewInfo(view);
+
+        assertThat(handwritableViewInfo.getView()).isEqualTo(view);
+
+        final boolean isViewInfoValid = handwritableViewInfo.update();
+
+        assertTrue(isViewInfoValid);
+        assertThat(handwritableViewInfo.getHandwritingArea()).isEqualTo(rect);
+        assertFalse(handwritableViewInfo.mIsDirty);
+    }
+
+    @Test
+    public void update_viewDisableAutoHandwriting() {
+        final Rect rect = new Rect(1, 2, 3, 4);
+        final View view = HandwritingTestUtil.createView(rect, false /* autoHandwritingEnabled */);
+        final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo =
+                new HandwritingInitiator.HandwritableViewInfo(view);
+
+        assertThat(handwritableViewInfo.getView()).isEqualTo(view);
+
+        final boolean isViewInfoValid = handwritableViewInfo.update();
+
+        // Return false because the view disabled autoHandwriting.
+        assertFalse(isViewInfoValid);
+        // The view disabled the autoHandwriting, and it won't update the handwriting area.
+        assertThat(handwritableViewInfo.getHandwritingArea()).isNull();
+    }
+
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java
new file mode 100644
index 0000000..db4707a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.stylus;
+
+import static android.view.stylus.HandwritingTestUtil.createView;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingInitiator;
+import android.view.View;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+
+/**
+ * Tests for {@link HandwritingInitiator.HandwritingAreaTracker}
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:android.view.stylus.HandwritingAreaTrackerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HandwritingAreaTrackerTest {
+    HandwritingInitiator.HandwritingAreaTracker mHandwritingAreaTracker;
+    Context mContext;
+
+    @Before
+    public void setup() {
+        final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getTargetContext();
+        mHandwritingAreaTracker = new HandwritingInitiator.HandwritingAreaTracker();
+    }
+
+    @Test
+    public void updateHandwritingAreaForView_singleView() {
+        Rect rect = new Rect(0, 0, 100, 100);
+        View view = createView(rect);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view);
+
+        List<HandwritingInitiator.HandwritableViewInfo> viewInfos =
+                mHandwritingAreaTracker.computeViewInfos();
+
+        assertThat(viewInfos.size()).isEqualTo(1);
+        assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect);
+        assertThat(viewInfos.get(0).getView()).isEqualTo(view);
+    }
+
+    @Test
+    public void updateHandwritingAreaForView_multipleViews() {
+        Rect rect1 = new Rect(0, 0, 100, 100);
+        Rect rect2 = new Rect(100, 100, 200, 200);
+
+        View view1 = createView(rect1);
+        View view2 = createView(rect2);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view1);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view2);
+
+        List<HandwritingInitiator.HandwritableViewInfo> viewInfos =
+                mHandwritingAreaTracker.computeViewInfos();
+
+        assertThat(viewInfos.size()).isEqualTo(2);
+        assertThat(viewInfos.get(0).getView()).isEqualTo(view1);
+        assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect1);
+
+        assertThat(viewInfos.get(1).getView()).isEqualTo(view2);
+        assertThat(viewInfos.get(1).getHandwritingArea()).isEqualTo(rect2);
+    }
+
+    @Test
+    public void updateHandwritingAreaForView_afterDisableAutoHandwriting() {
+        Rect rect1 = new Rect(0, 0, 100, 100);
+        Rect rect2 = new Rect(100, 100, 200, 200);
+
+        View view1 = createView(rect1);
+        View view2 = createView(rect2);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view1);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view2);
+
+        // There should be 2 views tracked.
+        assertThat(mHandwritingAreaTracker.computeViewInfos().size()).isEqualTo(2);
+
+        // Disable autoHandwriting for view1 and update handwriting area.
+        view1.setAutoHandwritingEnabled(false);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view1);
+
+        List<HandwritingInitiator.HandwritableViewInfo> viewInfos =
+                mHandwritingAreaTracker.computeViewInfos();
+        // The view1 has disabled the autoHandwriting, it's not tracked anymore.
+        assertThat(viewInfos.size()).isEqualTo(1);
+
+        // view2 is still tracked.
+        assertThat(viewInfos.get(0).getView()).isEqualTo(view2);
+        assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect2);
+    }
+
+    @Test
+    public void updateHandwritingAreaForView_removesInactiveView() {
+        Rect rect1 = new Rect(0, 0, 100, 100);
+        Rect rect2 = new Rect(100, 100, 200, 200);
+
+        View view1 = createView(rect1);
+        View view2 = createView(rect2);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view1);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view2);
+
+        // There should be 2 viewInfos tracked.
+        assertThat(mHandwritingAreaTracker.computeViewInfos().size()).isEqualTo(2);
+
+        // Disable autoHandwriting for view1, but update handwriting area for view2.
+        view1.setAutoHandwritingEnabled(false);
+        mHandwritingAreaTracker.updateHandwritingAreaForView(view2);
+
+        List<HandwritingInitiator.HandwritableViewInfo> viewInfos =
+                mHandwritingAreaTracker.computeViewInfos();
+        // The view1 has disabled the autoHandwriting, it's not tracked anymore.
+        assertThat(viewInfos.size()).isEqualTo(1);
+
+        // view2 is still tracked.
+        assertThat(viewInfos.get(0).getView()).isEqualTo(view2);
+        assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect2);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index e11fe17..1ae9649 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -19,6 +19,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.view.stylus.HandwritingTestUtil.createView;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -38,7 +39,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -78,7 +78,8 @@
         mHandwritingInitiator =
                 spy(new HandwritingInitiator(viewConfiguration, inputMethodManager));
 
-        mTestView = createMockView(sHwArea, true);
+        mTestView = createView(sHwArea, true);
+        mHandwritingInitiator.updateHandwritingAreasForView(mTestView);
     }
 
     @Test
@@ -195,8 +196,25 @@
     }
 
     @Test
+    public void onTouchEvent_focusView_stylusMoveOnce_withinHWArea() {
+        final int x1 = (sHwArea.left + sHwArea.right) / 2;
+        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + TOUCH_SLOP * 2;
+        final int y2 = y1;
+
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        // HandwritingInitiator will request focus for the registered view.
+        verify(mTestView, times(1)).requestFocus();
+    }
+
+    @Test
     public void autoHandwriting_whenDisabled_wontStartHW() {
-        View mockView = createMockView(sHwArea, false);
+        View mockView = createView(sHwArea, false);
         mHandwritingInitiator.onInputConnectionCreated(mockView);
         final int x1 = (sHwArea.left + sHwArea.right) / 2;
         final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
@@ -273,25 +291,4 @@
                 1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */,
                 InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */);
     }
-
-    private View createMockView(Rect viewBound, boolean autoHandwritingEnabled) {
-        // mock a parent so that HandwritingInitiator can get
-        ViewGroup parent = new ViewGroup(mContext) {
-            @Override
-            protected void onLayout(boolean changed, int l, int t, int r, int b) {
-                // We don't layout this view.
-            }
-            @Override
-            public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
-                r.set(viewBound);
-                return true;
-            }
-        };
-
-        View mockView = mock(View.class);
-        when(mockView.isAttachedToWindow()).thenReturn(true);
-        when(mockView.isAutoHandwritingEnabled()).thenReturn(autoHandwritingEnabled);
-        parent.addView(mockView);
-        return mockView;
-    }
 }
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
new file mode 100644
index 0000000..6daf880
--- /dev/null
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.stylus;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+public class HandwritingTestUtil {
+    public static View createView(Rect handwritingArea) {
+        return createView(handwritingArea, true);
+    }
+
+    public static View createView(Rect handwritingArea, boolean autoHandwritingEnabled) {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Context context = instrumentation.getTargetContext();
+        // mock a parent so that HandwritingInitiator can get
+        final ViewGroup parent = new ViewGroup(context) {
+            @Override
+            protected void onLayout(boolean changed, int l, int t, int r, int b) {
+                // We don't layout this view.
+            }
+            @Override
+            public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+                r.set(handwritingArea);
+                return true;
+            }
+        };
+
+        View view = spy(new View(context));
+        when(view.isAttachedToWindow()).thenReturn(true);
+        when(view.isAggregatedVisible()).thenReturn(true);
+        when(view.getHandwritingArea()).thenReturn(handwritingArea);
+        view.setAutoHandwritingEnabled(autoHandwritingEnabled);
+        parent.addView(view);
+        return view;
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 7f85982..139bc36 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -192,7 +192,7 @@
     }
 
     @Override
-    public Cursor queryResolver(ContentResolver resolver, Uri uri) {
+    public Cursor queryResolver(ContentResolver resolver, String[] projection, Uri uri) {
         if (sOverrides.resolverCursor != null) {
             return sOverrides.resolverCursor;
         }
@@ -201,7 +201,7 @@
             throw new SecurityException("Test exception handling");
         }
 
-        return super.queryResolver(resolver, uri);
+        return super.queryResolver(resolver, projection, uri);
     }
 
     @Override
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2d5f833..ac5daf0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -508,6 +508,8 @@
         <permission name="android.permission.MANAGE_APP_HIBERNATION"/>
         <!-- Permission required for CTS test - ResourceObserverNativeTest -->
         <permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
+        <!-- Permission required for CTS test - MediaCodecResourceTest -->
+        <permission name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
         <!-- Permission required for CTS test - CtsAlarmManagerTestCases -->
         <permission name="android.permission.SCHEDULE_PRIORITIZED_ALARM" />
         <!-- Permission required for CTS test - SystemMediaRouter2Test -->
@@ -547,17 +549,6 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.android.traceur">
-        <!-- Permissions required to receive BUGREPORT_STARTED intent -->
-        <permission name="android.permission.DUMP"/>
-        <!-- Permissions required to start/stop tracing -->
-        <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
-        <!-- Permissions required for quick settings tile -->
-        <permission name="android.permission.STATUS_BAR"/>
-        <!-- Permissions required to query Betterbug -->
-        <permission name="android.permission.QUERY_ALL_PACKAGES"/>
-    </privapp-permissions>
-
     <privapp-permissions package="com.android.tv">
         <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
         <permission name="android.permission.DVB_DEVICE"/>
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index dde757b..3ec5b9c 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -29,7 +29,7 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565})
+    @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565, R_8})
     public @interface Format { }
 
     // NOTE: these constants must match the values from graphics/common/x.x/types.hal
@@ -93,6 +93,9 @@
     /** @hide */
     public static final int HSV_888 = 0x37;
 
+    /** @hide */
+    public static final int R_8 = 0x38;
+
     /**
      * @deprecated use {@link android.graphics.ImageFormat#JPEG
      * ImageFormat.JPEG} instead.
@@ -142,6 +145,10 @@
                 info.bitsPerPixel = 64;
                 info.bytesPerPixel = 8;
                 break;
+            case R_8:
+                info.bitsPerPixel = 8;
+                info.bytesPerPixel = 1;
+                break;
             default:
                 throw new IllegalArgumentException("unknown pixel format " + format);
         }
@@ -235,6 +242,8 @@
                 return "HSV_888";
             case JPEG:
                 return "JPEG";
+            case R_8:
+                return "R_8";
             default:
                 return Integer.toString(format);
         }
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 8f368c2..d35ecbd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -924,6 +924,7 @@
      * Checks if an activity is embedded and its presentation is customized by a
      * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds.
      */
+    @Override
     public boolean isActivityEmbedded(@NonNull Activity activity) {
         return mPresenter.isActivityEmbedded(activity.getActivityToken());
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 9a6df23..4c505f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.back;
 
 import android.view.MotionEvent;
+import android.window.BackEvent;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
 
@@ -29,7 +30,7 @@
     /**
      * Called when a {@link MotionEvent} is generated by a back gesture.
      */
-    void onBackMotion(MotionEvent event);
+    void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge);
 
     /**
      * Sets whether the back gesture is past the trigger threshold or not.
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 a5140c3..32ac43d 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
@@ -36,6 +36,7 @@
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
+import android.window.BackEvent;
 import android.window.BackNavigationInfo;
 import android.window.IOnBackInvokedCallback;
 
@@ -127,8 +128,8 @@
         }
 
         @Override
-        public void onBackMotion(MotionEvent event) {
-            mShellExecutor.execute(() -> onMotionEvent(event));
+        public void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
+            mShellExecutor.execute(() -> onMotionEvent(event, swipeEdge));
         }
 
         @Override
@@ -183,12 +184,12 @@
      * Called when a new motion event needs to be transferred to this
      * {@link BackAnimationController}
      */
-    public void onMotionEvent(MotionEvent event) {
+    public void onMotionEvent(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
         int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             initAnimation(event);
         } else if (action == MotionEvent.ACTION_MOVE) {
-            onMove(event);
+            onMove(event, swipeEdge);
         } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
             onGestureFinished();
         }
@@ -264,7 +265,7 @@
         mTransaction.setVisibility(screenshotSurface, true);
     }
 
-    private void onMove(MotionEvent event) {
+    private void onMove(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
         if (!mBackGestureStarted || mBackNavigationInfo == null) {
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 1039e2a..51067a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -191,6 +191,19 @@
         return null;
     }
 
+    /**
+     * Gets a token associated with the view that can be used to grant the view focus.
+     */
+    public IBinder getFocusGrantToken(View view) {
+        SurfaceControlViewHost root = mViewRoots.get(view);
+        if (root == null) {
+            Slog.e(TAG, "Couldn't get focus grant token since view does not exist in "
+                    + "SystemWindow:" + view);
+            return null;
+        }
+        return root.getFocusGrantToken();
+    }
+
     private class PerDisplay {
         final int mDisplayId;
         private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index bdf9d51..7014fcc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -140,11 +140,8 @@
 
     /**
      * Whether the layout is eligible to be shown according to the internal state of the subclass.
-     * Returns true by default if subclass doesn't override this method.
      */
-    protected boolean eligibleToShowLayout() {
-        return true;
-    }
+    protected abstract boolean eligibleToShowLayout();
 
     @Override
     public void setConfiguration(Configuration configuration) {
@@ -214,8 +211,7 @@
         boolean layoutDirectionUpdated =
                 mTaskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection();
         if (boundsUpdated || layoutDirectionUpdated) {
-            // Reposition the UI surfaces.
-            updateSurfacePosition();
+            updateSurface();
         }
 
         if (layout != null && layoutDirectionUpdated) {
@@ -226,7 +222,6 @@
         return true;
     }
 
-
     /**
      * Updates the visibility of the layout.
      *
@@ -253,8 +248,7 @@
         displayLayout.getStableBounds(curStableBounds);
         mDisplayLayout = displayLayout;
         if (!prevStableBounds.equals(curStableBounds)) {
-            // Stable bounds changed, update UI surface positions.
-            updateSurfacePosition();
+            updateSurface();
             mStableBounds.set(curStableBounds);
         }
     }
@@ -304,6 +298,14 @@
     }
 
     /**
+     * Updates the surface following a change in the task bounds, display layout stable bounds,
+     * or the layout direction.
+     */
+    protected void updateSurface() {
+        updateSurfacePosition();
+    }
+
+    /**
      * Updates the position of the surface with respect to the task bounds and display layout
      * stable bounds.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
index b22b829..bc1d19b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -36,8 +36,6 @@
     // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
     // 204 is simply 255 * 0.8.
     static final int BACKGROUND_DIM_ALPHA = 204;
-
-    private LetterboxEduWindowManager mWindowManager;
     private View mDialogContainer;
     private Drawable mBackgroundDim;
 
@@ -58,10 +56,6 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    void inject(LetterboxEduWindowManager windowManager) {
-        mWindowManager = windowManager;
-    }
-
     View getDialogContainer() {
         return mDialogContainer;
     }
@@ -70,13 +64,6 @@
         return mBackgroundDim;
     }
 
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        // Need to relayout after visibility changes since they affect size.
-        mWindowManager.relayout();
-    }
-
     /**
      * Register a callback for the dismiss button and background dim.
      *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index d5aefb1..c461ebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -103,7 +103,6 @@
     protected View createLayout() {
         setSeenLetterboxEducation();
         mLayout = inflateLayout();
-        mLayout.inject(this);
 
         mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
                 this::setDismissOnClickListener);
@@ -145,15 +144,22 @@
     }
 
     @Override
+    protected void updateSurface() {
+        // We need to relayout because the layout dimensions depend on the task bounds.
+        relayout();
+    }
+
+    @Override
     protected void updateSurfacePosition(Rect taskBounds, Rect stableBounds) {
-        updateSurfacePosition(/* positionX= */ taskBounds.left, /* positionY= */ taskBounds.top);
+        // Nothing to do, since the position of the surface is fixed to the top left corner (0,0)
+        // of the task (parent surface), which is the default position of a surface.
     }
 
     @Override
     protected WindowManager.LayoutParams getWindowLayoutParams() {
         final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
-        return getWindowLayoutParams(/* width= */ taskBounds.right - taskBounds.left,
-                /* height= */ taskBounds.bottom - taskBounds.top);
+        return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */
+                taskBounds.height());
     }
 
     private boolean getHasSeenLetterboxEducation() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 5ebdceb..e8bae0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -45,10 +45,12 @@
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
 import android.content.ClipDescription;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.ResolveInfo;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -62,9 +64,11 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.lang.annotation.Retention;
@@ -106,12 +110,19 @@
      */
     void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
         mLoggerSessionId = loggerSessionId;
-        mSession = new DragSession(mContext, mActivityTaskManager, displayLayout, data);
+        mSession = new DragSession(mActivityTaskManager, displayLayout, data);
         // TODO(b/169894807): Also update the session data with task stack changes
         mSession.update();
     }
 
     /**
+     * Returns the last running task.
+     */
+    ActivityManager.RunningTaskInfo getLatestRunningTask() {
+        return mSession.runningTaskInfo;
+    }
+
+    /**
      * Returns the target's regions based on the current state of the device and display.
      */
     @NonNull
@@ -248,32 +259,68 @@
             final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
             mStarter.startShortcut(packageName, id, position, opts, user);
         } else {
-            mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT),
-                    null, position, opts);
+            final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+            mStarter.startIntent(launchIntent, getStartIntentFillInIntent(launchIntent, position),
+                    position, opts);
         }
     }
 
     /**
+     * Returns the fill-in intent to use when starting an app from a drop.
+     */
+    @VisibleForTesting
+    Intent getStartIntentFillInIntent(PendingIntent launchIntent, @SplitPosition int position) {
+        // Get the drag app
+        final List<ResolveInfo> infos = launchIntent.queryIntentComponents(0 /* flags */);
+        final ComponentName dragIntentActivity = !infos.isEmpty()
+                ? infos.get(0).activityInfo.getComponentName()
+                : null;
+
+        // Get the current app (either fullscreen or the remaining app post-drop if in splitscreen)
+        final boolean inSplitScreen = mSplitScreen != null
+                && mSplitScreen.isSplitScreenVisible();
+        final ComponentName currentActivity;
+        if (!inSplitScreen) {
+            currentActivity = mSession.runningTaskInfo != null
+                    ? mSession.runningTaskInfo.baseActivity
+                    : null;
+        } else {
+            final int nonReplacedSplitPosition = position == SPLIT_POSITION_TOP_OR_LEFT
+                    ? SPLIT_POSITION_BOTTOM_OR_RIGHT
+                    : SPLIT_POSITION_TOP_OR_LEFT;
+            ActivityManager.RunningTaskInfo nonReplacedTaskInfo =
+                    mSplitScreen.getTaskInfo(nonReplacedSplitPosition);
+            currentActivity = nonReplacedTaskInfo.baseActivity;
+        }
+
+        if (currentActivity.equals(dragIntentActivity)) {
+            // Only apply MULTIPLE_TASK if we are dragging the same activity
+            final Intent fillInIntent = new Intent();
+            fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Adding MULTIPLE_TASK");
+            return fillInIntent;
+        }
+        return null;
+    }
+
+    /**
      * Per-drag session data.
      */
     private static class DragSession {
-        private final Context mContext;
         private final ActivityTaskManager mActivityTaskManager;
         private final ClipData mInitialDragData;
 
         final DisplayLayout displayLayout;
         Intent dragData;
-        int runningTaskId;
+        ActivityManager.RunningTaskInfo runningTaskInfo;
         @WindowConfiguration.WindowingMode
         int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
         @WindowConfiguration.ActivityType
         int runningTaskActType = ACTIVITY_TYPE_STANDARD;
-        boolean runningTaskIsResizeable;
         boolean dragItemSupportsSplitscreen;
 
-        DragSession(Context context, ActivityTaskManager activityTaskManager,
+        DragSession(ActivityTaskManager activityTaskManager,
                 DisplayLayout dispLayout, ClipData data) {
-            mContext = context;
             mActivityTaskManager = activityTaskManager;
             mInitialDragData = data;
             displayLayout = dispLayout;
@@ -287,10 +334,9 @@
                     mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
             if (!tasks.isEmpty()) {
                 final ActivityManager.RunningTaskInfo task = tasks.get(0);
+                runningTaskInfo = task;
                 runningTaskWinMode = task.getWindowingMode();
                 runningTaskActType = task.getActivityType();
-                runningTaskId = task.taskId;
-                runningTaskIsResizeable = task.isResizeable;
             }
 
             final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 7307ba3..d395f95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -26,7 +26,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
 import android.app.StatusBarManager;
 import android.content.ClipData;
 import android.content.Context;
@@ -35,7 +34,6 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.RemoteException;
 import android.view.DragEvent;
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
@@ -51,7 +49,6 @@
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Coordinates the visible drop targets for the current drag.
@@ -166,17 +163,8 @@
         boolean alreadyInSplit = mSplitScreenController != null
                 && mSplitScreenController.isSplitScreenVisible();
         if (!alreadyInSplit) {
-            List<ActivityManager.RunningTaskInfo> tasks = null;
-            // Figure out the splashscreen info for the existing task.
-            try {
-                tasks = ActivityTaskManager.getService().getTasks(1,
-                        false /* filterOnlyVisibleRecents */,
-                        false /* keepIntentExtra */);
-            } catch (RemoteException e) {
-                // don't show an icon / will just use the defaults
-            }
-            if (tasks != null && !tasks.isEmpty()) {
-                ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
+            ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask();
+            if (taskInfo1 != null) {
                 Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
                 int bgColor1 = getResizingBackgroundColor(taskInfo1);
                 mDropZoneView1.setAppInfo(bgColor1, icon1);
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 6ec8f5b..71cff02 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
@@ -28,7 +28,6 @@
 import android.graphics.RectF;
 import android.os.Debug;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Size;
@@ -126,7 +125,6 @@
     private int mMenuState;
 
     private PipMenuView mPipMenuView;
-    private IBinder mPipMenuInputToken;
 
     private ActionListener mMediaActionListener = new ActionListener() {
         @Override
@@ -206,7 +204,6 @@
         mApplier = null;
         mSystemWindows.removeView(mPipMenuView);
         mPipMenuView = null;
-        mPipMenuInputToken = null;
     }
 
     /**
@@ -392,7 +389,6 @@
 
         if (mApplier == null) {
             mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
-            mPipMenuInputToken = mPipMenuView.getViewRootImpl().getInputToken();
         }
 
         return mApplier != null;
@@ -539,7 +535,8 @@
 
             try {
                 WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
-                        mPipMenuInputToken, menuState != MENU_STATE_NONE /* grantFocus */);
+                        mSystemWindows.getFocusGrantToken(mPipMenuView),
+                        menuState != MENU_STATE_NONE /* grantFocus */);
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to update focus as menu appears/disappears", e);
             }
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 32861b6..f838a0b 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
@@ -143,6 +143,7 @@
         mSystemWindows.addView(mPipMenuView,
                 getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                 0, SHELL_ROOT_LAYER_PIP);
+        mPipMenuView.setFocusGrantToken(mSystemWindows.getFocusGrantToken(mPipMenuView));
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 0141b6a..84eae9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -28,6 +28,7 @@
 import android.app.RemoteAction;
 import android.content.Context;
 import android.os.Handler;
+import android.os.IBinder;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
@@ -69,6 +70,7 @@
     private final ImageView mArrowRight;
     private final ImageView mArrowDown;
     private final ImageView mArrowLeft;
+    private IBinder mFocusGrantToken = null;
 
     public TvPipMenuView(@NonNull Context context) {
         this(context, null);
@@ -108,6 +110,10 @@
         mListener = listener;
     }
 
+    void setFocusGrantToken(IBinder token) {
+        mFocusGrantToken = token;
+    }
+
     void show(boolean inMoveMode, int gravity) {
         if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode);
         grantWindowFocus(true);
@@ -162,7 +168,7 @@
 
         try {
             WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
-                    getViewRootImpl().getInputToken(), grantFocus);
+                    mFocusGrantToken, grantFocus);
         } catch (Exception e) {
             Log.e(TAG, "Unable to update focus", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 219530b..029d073 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -163,6 +163,7 @@
     // and exit, since exit itself can trigger a number of changes that update the stages.
     private boolean mShouldUpdateRecents;
     private boolean mExitSplitScreenOnHide;
+    private boolean mIsDividerRemoteAnimating;
 
     /** The target stage to dismiss to when unlock after folded. */
     @StageType
@@ -389,6 +390,7 @@
                     RemoteAnimationTarget[] wallpapers,
                     RemoteAnimationTarget[] nonApps,
                     final IRemoteAnimationFinishedCallback finishedCallback) {
+                mIsDividerRemoteAnimating = true;
                 RemoteAnimationTarget[] augmentedNonApps =
                         new RemoteAnimationTarget[nonApps.length + 1];
                 for (int i = 0; i < nonApps.length; ++i) {
@@ -400,6 +402,7 @@
                         new IRemoteAnimationFinishedCallback.Stub() {
                             @Override
                             public void onAnimationFinished() throws RemoteException {
+                                mIsDividerRemoteAnimating = false;
                                 mShouldUpdateRecents = true;
                                 mSyncQueue.queue(evictWct);
                                 mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
@@ -423,6 +426,7 @@
 
             @Override
             public void onAnimationCancelled() {
+                mIsDividerRemoteAnimating = false;
                 mShouldUpdateRecents = true;
                 mSyncQueue.queue(evictWct);
                 mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
@@ -467,6 +471,7 @@
 
         // Using legacy transitions, so we can't use blast sync since it conflicts.
         mTaskOrganizer.applyTransaction(wct);
+        mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
     }
 
     /** Start an intent and a task ordered by {@code intentFirst}. */
@@ -1055,7 +1060,7 @@
 
     private void applyDividerVisibility(SurfaceControl.Transaction t) {
         final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
-        if (dividerLeash == null) return;
+        if (mIsDividerRemoteAnimating || dividerLeash == null) return;
 
         if (mDividerVisible) {
             t.show(dividerLeash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index ddf01a8..5af1530 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -201,6 +201,7 @@
                 "Display is changing, check if it should be seamless.");
         boolean checkedDisplayLayout = false;
         boolean hasTask = false;
+        boolean displayExplicitSeamless = false;
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
 
@@ -209,7 +210,6 @@
 
             // This container isn't rotating, so we can ignore it.
             if (change.getEndRotation() == change.getStartRotation()) continue;
-
             if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
                 // In the presence of System Alert windows we can not seamlessly rotate.
                 if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
@@ -217,6 +217,8 @@
                             "  display has system alert windows, so not seamless.");
                     return false;
                 }
+                displayExplicitSeamless =
+                        change.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS;
             } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
                 if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -268,8 +270,8 @@
             }
         }
 
-        // ROTATION_ANIMATION_SEAMLESS can only be requested by task.
-        if (hasTask) {
+        // ROTATION_ANIMATION_SEAMLESS can only be requested by task or display.
+        if (hasTask || displayExplicitSeamless) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Rotation IS seamless.");
             return true;
         }
@@ -417,8 +419,8 @@
                     // hasRoundedCorners is currently only enabled for tasks
                     final Context displayContext =
                             mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
-                    cornerRadius =
-                            ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
+                    cornerRadius = displayContext == null ? 0
+                            : ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
                 } else {
                     cornerRadius = 0;
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
index 7f8eaf1..7e95814 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
@@ -16,10 +16,14 @@
 
 package com.android.wm.shell.util;
 
+import android.graphics.Point;
+import android.util.RotationUtils;
 import android.view.SurfaceControl;
 
 /**
- * Utility class that takes care of counter-rotating surfaces during a transition animation.
+ * Utility class that takes care of rotating unchanging child-surfaces to match the parent rotation
+ * during a transition animation. This gives the illusion that the child surfaces haven't rotated
+ * relative to the screen.
  */
 public class CounterRotator {
     private SurfaceControl mSurface = null;
@@ -33,29 +37,30 @@
      * Sets up this rotator.
      *
      * @param rotateDelta is the forward rotation change (the rotation the display is making).
-     * @param displayW (and H) Is the size of the rotating display.
+     * @param parentW (and H) Is the size of the rotating parent after the rotation.
      */
     public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
-            float displayW, float displayH) {
+            float parentW, float parentH) {
         if (rotateDelta == 0) return;
-        // We want to counter-rotate, so subtract from 4
-        rotateDelta = 4 - (rotateDelta + 4) % 4;
         mSurface = new SurfaceControl.Builder()
                 .setName("Transition Unrotate")
                 .setContainerLayer()
                 .setParent(parent)
                 .build();
-        // column-major
-        if (rotateDelta == 1) {
-            t.setMatrix(mSurface, 0, 1, -1, 0);
-            t.setPosition(mSurface, displayW, 0);
-        } else if (rotateDelta == 2) {
-            t.setMatrix(mSurface, -1, 0, 0, -1);
-            t.setPosition(mSurface, displayW, displayH);
-        } else if (rotateDelta == 3) {
-            t.setMatrix(mSurface, 0, -1, 1, 0);
-            t.setPosition(mSurface, 0, displayH);
+        // Rotate forward to match the new rotation (rotateDelta is the forward rotation the parent
+        // already took). Child surfaces will be in the old rotation relative to the new parent
+        // rotation, so we need to forward-rotate the child surfaces to match.
+        RotationUtils.rotateSurface(t, mSurface, rotateDelta);
+        final Point tmpPt = new Point(0, 0);
+        // parentW/H are the size in the END rotation, the rotation utilities expect the starting
+        // size. So swap them if necessary
+        if ((rotateDelta % 2) != 0) {
+            final float w = parentW;
+            parentW = parentH;
+            parentH = w;
         }
+        RotationUtils.rotatePoint(tmpPt, rotateDelta, (int) parentW, (int) parentH);
+        t.setPosition(mSurface, tmpPt.x, tmpPt.y);
         t.show(mSurface);
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 99f7e23..278ba9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -68,9 +68,11 @@
             }
 
             teardown {
-                notifyManager.setBubblesAllowed(testApp.component.packageName,
+                test {
+                    notifyManager.setBubblesAllowed(testApp.component.packageName,
                         uid, NotificationManager.BUBBLE_PREFERENCE_NONE)
-                testApp.exit()
+                    testApp.exit()
+                }
             }
 
             extraSpec(this)
@@ -95,7 +97,7 @@
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                            repetitions = 5)
+                            repetitions = 3)
         }
 
         const val FIND_OBJECT_TIMEOUT = 2000L
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index a928bbd..f6abc75 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -28,6 +28,9 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
 import org.junit.runner.RunWith
 import org.junit.Test
 import org.junit.runners.Parameterized
@@ -44,11 +47,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @Group4
-class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
     private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
     private val displaySize = DisplayMetrics()
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
@@ -68,7 +76,7 @@
 
     @Presubmit
     @Test
-    fun testAppIsAlwaysVisible() {
+    open fun testAppIsAlwaysVisible() {
         testSpec.assertLayers {
             this.isVisible(testApp.component)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt
new file mode 100644
index 0000000..dd744b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bubble
+
+import androidx.test.filters.FlakyTest
+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.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
[email protected](FlickerParametersRunnerFactory::class)
+@Group4
+@FlakyTest(bugId = 217777115)
+class DismissBubbleScreenShellTransit(
+    testSpec: FlickerTestParameter
+) : DismissBubbleScreen(testSpec) {
+    @Before
+    override fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index af629cc..2ec743c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -24,6 +24,9 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
 import org.junit.runner.RunWith
 import org.junit.Test
 import org.junit.runners.Parameterized
@@ -42,7 +45,12 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @Group4
-class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
 
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
@@ -61,7 +69,7 @@
 
     @Presubmit
     @Test
-    fun testAppIsAlwaysVisible() {
+    open fun testAppIsAlwaysVisible() {
         testSpec.assertLayers {
             this.isVisible(testApp.component)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt
new file mode 100644
index 0000000..d92ec77
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import androidx.test.filters.FlakyTest
+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.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
[email protected](FlickerParametersRunnerFactory::class)
+@Group4
+@FlakyTest(bugId = 217777115)
+class ExpandBubbleScreenShellTransit(
+    testSpec: FlickerTestParameter
+) : ExpandBubbleScreen(testSpec) {
+    @Before
+    override fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index 64636be..a20a201 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -22,6 +22,9 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
 import org.junit.runner.RunWith
 import org.junit.Test
 import org.junit.runners.Parameterized
@@ -39,7 +42,12 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @Group4
-class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
 
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
@@ -51,7 +59,7 @@
 
     @Presubmit
     @Test
-    fun testAppIsAlwaysVisible() {
+    open fun testAppIsAlwaysVisible() {
         testSpec.assertLayers {
             this.isVisible(testApp.component)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt
new file mode 100644
index 0000000..9350868
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import androidx.test.filters.FlakyTest
+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.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
[email protected](FlickerParametersRunnerFactory::class)
+@Group4
+@FlakyTest(bugId = 217777115)
+class LaunchBubbleScreenShellTransit(
+    testSpec: FlickerTestParameter
+) : LaunchBubbleScreen(testSpec) {
+    @Before
+    override fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index add11c1..8d1e315 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -25,6 +25,9 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
 import org.junit.runner.RunWith
 import org.junit.Test
 import org.junit.runners.Parameterized
@@ -41,7 +44,12 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @Group4
-class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
 
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
@@ -69,7 +77,7 @@
 
     @Presubmit
     @Test
-    fun testAppIsAlwaysVisible() {
+    open fun testAppIsAlwaysVisible() {
         testSpec.assertLayers {
             this.isVisible(testApp.component)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
new file mode 100644
index 0000000..ddebb6f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import androidx.test.filters.FlakyTest
+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.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
[email protected](FlickerParametersRunnerFactory::class)
+@Group4
+@FlakyTest(bugId = 217777115)
+class MultiBubblesScreenShellTransit(
+    testSpec: FlickerTestParameter
+) : MultiBubblesScreen(testSpec) {
+    @Before
+    override fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index db94de2..467cadc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -71,24 +71,6 @@
             }
         }
 
-    @FlakyTest
-    @Test
-    fun runPresubmitAssertion() {
-        flickerRule.checkPresubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runPostsubmitAssertion() {
-        flickerRule.checkPostsubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runFlakyAssertion() {
-        flickerRule.checkFlakyAssertions()
-    }
-
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
@@ -205,7 +187,7 @@
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                    repetitions = 5)
+                    repetitions = 3)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index afe64e3..accb524 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -224,7 +224,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                    repetitions = 5)
+                    repetitions = 3)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 3a9a070..931d060 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -98,7 +98,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+                    supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 03c8929f..e00d749 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -78,24 +78,6 @@
             }
         }
 
-    @FlakyTest
-    @Test
-    fun runPresubmitAssertion() {
-        flickerRule.checkPresubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runPostsubmitAssertion() {
-        flickerRule.checkPostsubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runFlakyAssertion() {
-        flickerRule.checkFlakyAssertions()
-    }
-
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
@@ -117,7 +99,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+                supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 976b7c6..5214daa0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -67,24 +67,6 @@
             }
         }
 
-    @FlakyTest
-    @Test
-    fun runPresubmitAssertion() {
-        flickerRule.checkPresubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runPostsubmitAssertion() {
-        flickerRule.checkPostsubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runFlakyAssertion() {
-        flickerRule.checkFlakyAssertions()
-    }
-
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
@@ -107,7 +89,7 @@
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                            repetitions = 5)
+                            repetitions = 3)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 9061239..332bba6a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -93,7 +93,7 @@
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                            repetitions = 5)
+                            repetitions = 3)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 8d14f70..07c3b15 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -173,7 +173,7 @@
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                            repetitions = 5)
+                            repetitions = 3)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index e415024..e91bef1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -96,7 +96,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+                    supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 4a4c46c..7e66cd3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -96,7 +96,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+                supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 1d61ab4..7a1d619 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -31,7 +31,7 @@
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.wm.shell.flicker.helpers.ImeAppHelper
 import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,9 +47,14 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
-class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     private val imeApp = ImeAppHelper(instrumentation)
 
+    @Before
+    open fun before() {
+        assumeFalse(isShellTransitionsEnabled)
+    }
+
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = false) {
             setup {
@@ -77,25 +82,14 @@
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
-
-    @FlakyTest(bugId = 214452854)
-    @Test
-    fun statusBarLayerRotatesScales_shellTransit() {
-        assumeTrue(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /**
      * Ensure the pip window remains visible throughout any keyboard interactions
      */
     @Presubmit
     @Test
-    fun pipInVisibleBounds() {
+    open fun pipInVisibleBounds() {
         testSpec.assertWmVisibleRegion(pipApp.component) {
             val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
             coversAtMost(displayBounds)
@@ -107,7 +101,7 @@
      */
     @Presubmit
     @Test
-    fun pipIsAboveAppWindow() {
+    open fun pipIsAboveAppWindow() {
         testSpec.assertWmTag(TAG_IME_VISIBLE) {
             isAboveWindow(FlickerComponentName.IME, pipApp.component)
         }
@@ -121,7 +115,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                    repetitions = 5)
+                    repetitions = 3)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
new file mode 100644
index 0000000..1a21d32
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.pip
+
+import androidx.test.filters.FlakyTest
+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.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+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
+
+@RequiresDevice
+@RunWith(Parameterized::class)
[email protected](FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+@FlakyTest(bugId = 217777115)
+class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
+
+    @Before
+    override fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index b2b50ad..27873bb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -27,10 +27,13 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -61,11 +64,16 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
-class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     private val fixedApp = FixedAppHelper(instrumentation)
     private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
     private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = false) {
             setup {
@@ -182,7 +190,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigRotationTests(
                 supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
-                repetitions = 5)
+                repetitions = 3)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
new file mode 100644
index 0000000..a017f56
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import androidx.test.filters.FlakyTest
+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.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
[email protected](FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+@FlakyTest(bugId = 217777115)
+class PipRotationTestShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+    @Before
+    override fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 8dd9104..d35654e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -26,9 +26,12 @@
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
 import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
+import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,12 +47,17 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
-class SetRequestedOrientationWhilePinnedTest(
+open class SetRequestedOrientationWhilePinnedTest(
     testSpec: FlickerTestParameter
 ) : PipTransition(testSpec) {
     private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
     private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setupAndTeardown(this)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt
new file mode 100644
index 0000000..36e2804
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import androidx.test.filters.FlakyTest
+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.annotation.Group4
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
[email protected](FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+@FlakyTest(bugId = 217777115)
+class SetRequestedOrientationWhilePinnedTestShellTransit(
+    testSpec: FlickerTestParameter
+) : SetRequestedOrientationWhilePinnedTest(testSpec) {
+    @Before
+    override fun before() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index 6080f3a..403dbf9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -22,7 +22,7 @@
 import android.hardware.display.DisplayManager;
 import android.testing.TestableContext;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 21ced0d..b11f910 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -30,6 +30,7 @@
 import android.testing.AndroidTestingRunner;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
+import android.window.BackEvent;
 import android.window.BackNavigationInfo;
 
 import androidx.test.filters.SmallTest;
@@ -94,7 +95,9 @@
         SurfaceControl screenshotSurface = new SurfaceControl();
         HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
         createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer);
-        mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
+        mController.onMotionEvent(
+                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
+                BackEvent.EDGE_LEFT);
         verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
         verify(mTransaction).setVisibility(screenshotSurface, true);
         verify(mTransaction).apply();
@@ -106,8 +109,12 @@
         SurfaceControl screenshotSurface = new SurfaceControl();
         HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
         createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer);
-        mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
-        mController.onMotionEvent(MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0));
+        mController.onMotionEvent(
+                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
+                BackEvent.EDGE_LEFT);
+        mController.onMotionEvent(
+                MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
+                BackEvent.EDGE_LEFT);
         verify(mTransaction).setPosition(topWindowLeash, 100, 100);
         verify(mTransaction, atLeastOnce()).apply();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 35e4982..bb6026c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -23,6 +23,8 @@
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -32,6 +34,7 @@
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
 
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -50,9 +53,11 @@
 import android.app.PendingIntent;
 import android.content.ClipData;
 import android.content.ClipDescription;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Insets;
@@ -177,6 +182,12 @@
         info.configuration.windowConfiguration.setActivityType(actType);
         info.configuration.windowConfiguration.setWindowingMode(winMode);
         info.isResizeable = true;
+        info.baseActivity = new ComponentName(getInstrumentation().getContext().getPackageName(),
+                ".ActivityWithMode" + winMode);
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = info.baseActivity.getPackageName();
+        activityInfo.name = info.baseActivity.getClassName();
+        info.topActivityInfo = activityInfo;
         return info;
     }
 
@@ -252,6 +263,62 @@
         }
     }
 
+    @Test
+    public void testLaunchMultipleTask_differentActivity() {
+        setRunningTask(mFullscreenAppTask);
+        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
+        assertNull(fillInIntent);
+    }
+
+    @Test
+    public void testLaunchMultipleTask_differentActivity_inSplitscreen() {
+        setRunningTask(mFullscreenAppTask);
+        doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
+        doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
+        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
+        assertNull(fillInIntent);
+    }
+
+    @Test
+    public void testLaunchMultipleTask_sameActivity() {
+        setRunningTask(mFullscreenAppTask);
+
+        // Replace the mocked drag pending intent and ensure it resolves to the same activity
+        PendingIntent launchIntent = mock(PendingIntent.class);
+        ResolveInfo launchInfo = new ResolveInfo();
+        launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
+        doReturn(Collections.singletonList(launchInfo))
+                .when(launchIntent).queryIntentComponents(anyInt());
+        mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
+                launchIntent);
+
+        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
+        assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
+    }
+
+    @Test
+    public void testLaunchMultipleTask_sameActivity_inSplitScreen() {
+        setRunningTask(mFullscreenAppTask);
+
+        // Replace the mocked drag pending intent and ensure it resolves to the same activity
+        PendingIntent launchIntent = mock(PendingIntent.class);
+        ResolveInfo launchInfo = new ResolveInfo();
+        launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
+        doReturn(Collections.singletonList(launchInfo))
+                .when(launchIntent).queryIntentComponents(anyInt());
+        mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
+                launchIntent);
+
+        doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
+        doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
+        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
+        assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
+    }
+
     private Target filterTargetByType(ArrayList<Target> targets, int type) {
         for (Target t : targets) {
             if (type == t.type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index f10dc16..b976c12 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -24,8 +24,8 @@
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.common.ShellExecutor;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
index 078e2b6..16e9239 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -45,8 +45,8 @@
 import android.window.IWindowContainerToken;
 import android.window.WindowContainerToken;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 0f4a06f..dbf93b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -591,6 +591,13 @@
                         .setRotate().build())
                 .build();
         assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays));
+
+        // Seamless if display is explicitly seamless.
+        final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+                        .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+                .build();
+        assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays));
     }
 
     class TransitionInfoBuilder {
diff --git a/media/Android.bp b/media/Android.bp
index 5aedcfb..2f9c520 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -103,6 +103,11 @@
         },
         java: {
             sdk_version: "module_current",
+            min_sdk_version: "29",
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.car.framework",
+            ],
         },
         ndk: {
             vndk: {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index cdc3163..3887372 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -91,6 +91,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * AudioManager provides access to volume and ringer mode control.
@@ -8342,6 +8343,106 @@
         }
     }
 
+    /**
+     * Add UID's that can be considered as assistant.
+     *
+     * @param assistantUids UID's of the services that can be considered as assistant.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void addAssistantServicesUids(@NonNull List<Integer> assistantUids) {
+        try {
+            getService().addAssistantServicesUids(assistantUids.stream()
+                    .mapToInt(Integer::intValue).toArray());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove UID's that can be considered as assistant.
+     *
+     * @param assistantUids UID'S of the services that should be remove.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void removeAssistantServicesUids(@NonNull List<Integer> assistantUids) {
+        try {
+            getService().removeAssistantServicesUids(assistantUids.stream()
+                    .mapToInt(Integer::intValue).toArray());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the list of assistants UIDs that been added with the
+     * {@link #addAssistantServicesUids(List)} (List)} and not yet removed with
+     * {@link #removeAssistantServicesUids(List)}
+     *
+     * @return list of assistants UID's
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull List<Integer> getAssistantServicesUids() {
+        try {
+            int[] uids = getService().getAssistantServicesUids();
+            return Arrays.stream(uids).boxed().collect(Collectors.toList());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets UID's that can be considered as active assistant. Calling the API with a new list will
+     * overwrite previous list. If the list of UIDs is empty then no UID will be considered active.
+     * In this manner calling the API with an empty list will remove all UID's previously set.
+     *
+     * @param assistantUids UID'S of the services that can be considered active assistant. Can be
+     * an empty list, for this no UID will be considered active.
+     *
+     * <p> Note that during audio service crash reset and after boot up the list of active assistant
+     * UID's will be reset to an empty list (i.e. no UID will be considered as an active assistant).
+     * Just after user switch the list of active assistant will also reset to empty.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setActiveAssistantServiceUids(@NonNull List<Integer>  assistantUids) {
+        try {
+            getService().setActiveAssistantServiceUids(assistantUids.stream()
+                    .mapToInt(Integer::intValue).toArray());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the list of active assistant UIDs last set with the
+     * {@link #setActiveAssistantServiceUids(List)}
+     *
+     * @return list of active assistants UID's
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull List<Integer> getActiveAssistantServicesUids() {
+        try {
+            int[] uids = getService().getActiveAssistantServiceUids();
+            return Arrays.stream(uids).boxed().collect(Collectors.toList());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private final Object mMuteAwaitConnectionListenerLock = new Object();
 
     @GuardedBy("mMuteAwaitConnectionListenerLock")
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index cb887f2..c263245 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -38,20 +38,29 @@
 
     public abstract void updateRingerModeAffectedStreamsInternal();
 
-    /**
-     * Notify the UID of the currently active {@link android.service.voice.HotwordDetectionService}.
-     *
-     * <p>The caller is expected to take care of any performance implications, e.g. by using a
-     * background thread to call this method.</p>
-     *
-     * @param uid UID of the currently active service or {@link android.os.Process#INVALID_UID} if
-     *            none.
-     */
-    public abstract void setHotwordDetectionServiceUid(int uid);
-
     public abstract void setAccessibilityServiceUids(IntArray uids);
 
     /**
+     * Add the UID for a new assistant service
+     *
+     * @param uid UID of the newly available assistants
+     */
+    public abstract void addAssistantServiceUid(int uid);
+
+    /**
+     * Remove the UID for an existing assistant service
+     *
+     * @param uid UID of the currently available assistant
+     */
+    public abstract void removeAssistantServiceUid(int uid);
+
+    /**
+     * Set the currently active assistant service UIDs
+     * @param activeUids active UIDs of the assistant service
+     */
+    public abstract void setActiveAssistantServicesUids(IntArray activeUids);
+
+    /**
      * Called by {@link com.android.server.inputmethod.InputMethodManagerService} to notify the UID
      * of the currently used {@link android.inputmethodservice.InputMethodService}.
      *
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 536b4ad..6cacebb 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1856,16 +1856,15 @@
 
     /**
      * @hide
-     * Communicate UID of active assistant to audio policy service.
+     * Communicate UIDs of the active assistant to audio policy service.
      */
-    public static native int setAssistantUid(int uid);
+    public static native int setActiveAssistantServicesUids(int[] uids);
 
     /**
-     * Communicate UID of the current {@link android.service.voice.HotwordDetectionService} to audio
-     * policy service.
      * @hide
+     * Communicate UIDs of assistant to audio policy service.
      */
-    public static native int setHotwordDetectionServiceUid(int uid);
+    public static native int setAssistantServicesUids(int[] uids);
 
     /**
      * @hide
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index fec14de..8ba3539 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -465,4 +465,19 @@
     List<AudioFocusInfo> getFocusStack();
 
     boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    void addAssistantServicesUids(in int[] assistantUID);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    void removeAssistantServicesUids(in int[] assistantUID);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    void setActiveAssistantServiceUids(in int[] activeUids);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    int[] getAssistantServicesUids();
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    int[] getActiveAssistantServiceUids();
 }
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 939b679..4563259 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -16,9 +16,12 @@
 
 package android.media;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
@@ -1934,12 +1937,41 @@
     @NonNull
     public static MediaCodec createByCodecName(@NonNull String name)
             throws IOException {
-        return new MediaCodec(
-                name, false /* nameIsType */, false /* unused */);
+        return new MediaCodec(name, false /* nameIsType */, false /* encoder */);
     }
 
-    private MediaCodec(
-            @NonNull String name, boolean nameIsType, boolean encoder) {
+    /**
+     * This is the same as createByCodecName, but allows for instantiating a codec on behalf of a
+     * client process. This is used for system apps or system services that create MediaCodecs on
+     * behalf of other processes and will reclaim resources as necessary from processes with lower
+     * priority than the client process, rather than processes with lower priority than the system
+     * app or system service. Likely to be used with information obtained from
+     * {@link android.media.MediaCodecList}.
+     * @param name
+     * @param clientPid
+     * @param clientUid
+     * @throws IOException if the codec cannot be created.
+     * @throws IllegalArgumentException if name is not valid.
+     * @throws NullPointerException if name is null.
+     * @throws SecurityException if the MEDIA_RESOURCE_OVERRIDE_PID permission is not granted.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MEDIA_RESOURCE_OVERRIDE_PID)
+    public static MediaCodec createByCodecNameForClient(@NonNull String name, int clientPid,
+            int clientUid) throws IOException {
+        return new MediaCodec(name, false /* nameIsType */, false /* encoder */, clientPid,
+                clientUid);
+    }
+
+    private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder) {
+        this(name, nameIsType, encoder, -1 /* pid */, -1 /* uid */);
+    }
+
+    private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder, int pid,
+            int uid) {
         Looper looper;
         if ((looper = Looper.myLooper()) != null) {
             mEventHandler = new EventHandler(this, looper);
@@ -1957,7 +1989,7 @@
         // save name used at creation
         mNameAtCreation = nameIsType ? null : name;
 
-        native_setup(name, nameIsType, encoder);
+        native_setup(name, nameIsType, encoder, pid, uid);
     }
 
     private String mNameAtCreation;
@@ -4991,7 +5023,7 @@
     private static native final void native_init();
 
     private native final void native_setup(
-            @NonNull String name, boolean nameIsType, boolean encoder);
+            @NonNull String name, boolean nameIsType, boolean encoder, int pid, int uid);
 
     private native final void native_finalize();
 
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 73e96a2..96809bd 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1877,6 +1877,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
     public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase,
             @Nullable String sessionId) {
         return getClientPriorityInternal(useCase, sessionId);
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index f694482..c8d2d1e 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -202,7 +202,7 @@
 
 JMediaCodec::JMediaCodec(
         JNIEnv *env, jobject thiz,
-        const char *name, bool nameIsType, bool encoder)
+        const char *name, bool nameIsType, bool encoder, int pid, int uid)
     : mClass(NULL),
       mObject(NULL) {
     jclass clazz = env->GetObjectClass(thiz);
@@ -220,12 +220,12 @@
             ANDROID_PRIORITY_VIDEO);
 
     if (nameIsType) {
-        mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus);
+        mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus, pid, uid);
         if (mCodec == nullptr || mCodec->getName(&mNameAtCreation) != OK) {
             mNameAtCreation = "(null)";
         }
     } else {
-        mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus);
+        mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus, pid, uid);
         mNameAtCreation = name;
     }
     CHECK((mCodec != NULL) != (mInitStatus != OK));
@@ -3136,7 +3136,7 @@
 
 static void android_media_MediaCodec_native_setup(
         JNIEnv *env, jobject thiz,
-        jstring name, jboolean nameIsType, jboolean encoder) {
+        jstring name, jboolean nameIsType, jboolean encoder, int pid, int uid) {
     if (name == NULL) {
         jniThrowException(env, "java/lang/NullPointerException", NULL);
         return;
@@ -3148,24 +3148,33 @@
         return;
     }
 
-    sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
+    sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder, pid, uid);
 
     const status_t err = codec->initCheck();
     if (err == NAME_NOT_FOUND) {
         // fail and do not try again.
         jniThrowException(env, "java/lang/IllegalArgumentException",
-                String8::format("Failed to initialize %s, error %#x", tmp, err));
+                String8::format("Failed to initialize %s, error %#x (NAME_NOT_FOUND)", tmp, err));
         env->ReleaseStringUTFChars(name, tmp);
         return;
-    } if (err == NO_MEMORY) {
+    }
+    if (err == NO_MEMORY) {
         throwCodecException(env, err, ACTION_CODE_TRANSIENT,
-                String8::format("Failed to initialize %s, error %#x", tmp, err));
+                String8::format("Failed to initialize %s, error %#x (NO_MEMORY)", tmp, err));
         env->ReleaseStringUTFChars(name, tmp);
         return;
-    } else if (err != OK) {
+    }
+    if (err == PERMISSION_DENIED) {
+        jniThrowException(env, "java/lang/SecurityException",
+                String8::format("Failed to initialize %s, error %#x (PERMISSION_DENIED)", tmp,
+                err));
+        env->ReleaseStringUTFChars(name, tmp);
+        return;
+    }
+    if (err != OK) {
         // believed possible to try again
         jniThrowException(env, "java/io/IOException",
-                String8::format("Failed to find matching codec %s, error %#x", tmp, err));
+                String8::format("Failed to find matching codec %s, error %#x (?)", tmp, err));
         env->ReleaseStringUTFChars(name, tmp);
         return;
     }
@@ -3174,7 +3183,7 @@
 
     codec->registerSelf();
 
-    setMediaCodec(env,thiz, codec);
+    setMediaCodec(env, thiz, codec);
 }
 
 static void android_media_MediaCodec_native_finalize(
@@ -3478,7 +3487,7 @@
 
     { "native_init", "()V", (void *)android_media_MediaCodec_native_init },
 
-    { "native_setup", "(Ljava/lang/String;ZZ)V",
+    { "native_setup", "(Ljava/lang/String;ZZII)V",
       (void *)android_media_MediaCodec_native_setup },
 
     { "native_finalize", "()V",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index ee456c9..616c31b 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -55,7 +55,7 @@
 struct JMediaCodec : public AHandler {
     JMediaCodec(
             JNIEnv *env, jobject thiz,
-            const char *name, bool nameIsType, bool encoder);
+            const char *name, bool nameIsType, bool encoder, int pid, int uid);
 
     status_t initCheck() const;
 
diff --git a/native/android/OWNERS b/native/android/OWNERS
index 02dfd39..cfe9734 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -1,14 +1,23 @@
[email protected]
[email protected] #{LAST_RESORT_SUGGESTION}
 
+# General NDK API reviewers
+per-file libandroid.map.txt = [email protected], [email protected], [email protected]
+per-file libandroid.map.txt = [email protected], [email protected]
+
+# Networking
 per-file libandroid_net.map.txt, net.c = set noparent
 per-file libandroid_net.map.txt, net.c = [email protected], [email protected], [email protected]
 per-file libandroid_net.map.txt, net.c = [email protected], [email protected], [email protected]
+
+# Fonts
 per-file system_fonts.cpp = file:/graphics/java/android/graphics/fonts/OWNERS
 
+# Window manager
 per-file native_window_jni.cpp = file:/services/core/java/com/android/server/wm/OWNERS
 per-file native_activity.cpp = file:/services/core/java/com/android/server/wm/OWNERS
 per-file surface_control.cpp = file:/services/core/java/com/android/server/wm/OWNERS
 
+# Graphics
 per-file choreographer.cpp = file:/graphics/java/android/graphics/OWNERS
 per-file hardware_buffer_jni.cpp = file:/graphics/java/android/graphics/OWNERS
 per-file native_window_jni.cpp = file:/graphics/java/android/graphics/OWNERS
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index d01a30e..6eff629 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -141,7 +141,7 @@
 }
 
 struct ASurfaceControlStats {
-    int64_t acquireTime;
+    std::variant<int64_t, sp<Fence>> acquireTimeOrFence;
     sp<Fence> previousReleaseFence;
     uint64_t frameNumber;
 };
@@ -153,7 +153,7 @@
                                                const SurfaceStats& surfaceStats) {
         ASurfaceControlStats aSurfaceControlStats;
 
-        aSurfaceControlStats.acquireTime = surfaceStats.acquireTime;
+        aSurfaceControlStats.acquireTimeOrFence = surfaceStats.acquireTimeOrFence;
         aSurfaceControlStats.previousReleaseFence = surfaceStats.previousReleaseFence;
         aSurfaceControlStats.frameNumber = surfaceStats.eventStats.frameNumber;
 
@@ -171,7 +171,15 @@
 }
 
 int64_t ASurfaceControlStats_getAcquireTime(ASurfaceControlStats* stats) {
-    return stats->acquireTime;
+    if (const auto* fence = std::get_if<sp<Fence>>(&stats->acquireTimeOrFence)) {
+        // We got a fence instead of the acquire time due to latch unsignaled.
+        // Ideally the client could just get the acquire time dericly from
+        // the fence instead of calling this function which needs to block.
+        (*fence)->waitForever("ASurfaceControlStats_getAcquireTime");
+        return (*fence)->getSignalTime();
+    }
+
+    return std::get<int64_t>(stats->acquireTimeOrFence);
 }
 
 uint64_t ASurfaceControlStats_getFrameNumber(ASurfaceControlStats* stats) {
@@ -250,7 +258,7 @@
             aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
             "ASurfaceControl not found");
 
-    return aSurfaceControlStats->second.acquireTime;
+    return ASurfaceControlStats_getAcquireTime(&aSurfaceControlStats->second);
 }
 
 int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
@@ -294,9 +302,10 @@
 
         auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
 
-        for (const auto& [surfaceControl, latchTime, acquireTime, presentFence, previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) {
+        for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence,
+                          previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) {
             ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
-            aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
+            aSurfaceControlStats[aSurfaceControl].acquireTimeOrFence = acquireTimeOrFence;
             aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
         }
 
@@ -643,13 +652,12 @@
                 aSurfaceTransactionStats.transactionCompleted = false;
 
                 auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
-                for (const auto&
-                             [surfaceControl, latchTime, acquireTime, presentFence,
-                              previousReleaseFence, transformHint,
-                              frameEvents] : surfaceControlStats) {
+                for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence,
+                                  previousReleaseFence, transformHint, frameEvents] :
+                     surfaceControlStats) {
                     ASurfaceControl* aSurfaceControl =
                             reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
-                    aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
+                    aSurfaceControlStats[aSurfaceControl].acquireTimeOrFence = acquireTimeOrFence;
                 }
 
                 (*func)(callback_context, &aSurfaceTransactionStats);
diff --git a/omapi/aidl/vts/functional/config/Android.bp b/omapi/aidl/vts/functional/config/Android.bp
new file mode 100644
index 0000000..7c08257
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+xsd_config {
+    name: "omapi_uuid_map_config",
+    srcs: ["omapi_uuid_map_config.xsd"],
+    api_dir: "schema",
+    package_name: "omapi.uuid.map.config",
+}
diff --git a/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd b/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd
new file mode 100644
index 0000000..ffeb7a0
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd
@@ -0,0 +1,40 @@
+<?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.
+-->
+<xs:schema version="2.0"
+           attributeFormDefault="unqualified"
+           elementFormDefault="qualified"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="ref_do">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="uuid_ref_do" maxOccurs="unbounded" minOccurs="0">
+          <xs:complexType>
+            <xs:sequence>
+              <xs:element name="uids">
+                <xs:complexType>
+                  <xs:sequence>
+                    <xs:element type="xs:short" name="uid" maxOccurs="unbounded" minOccurs="0"/>
+                  </xs:sequence>
+                </xs:complexType>
+              </xs:element>
+              <xs:element type="xs:string" name="uuid"/>
+            </xs:sequence>
+          </xs:complexType>
+        </xs:element>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>
diff --git a/omapi/aidl/vts/functional/config/schema/current.txt b/omapi/aidl/vts/functional/config/schema/current.txt
new file mode 100644
index 0000000..c2e930b
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/schema/current.txt
@@ -0,0 +1,30 @@
+// Signature format: 2.0
+package omapi.uuid.map.config {
+
+  public class RefDo {
+    ctor public RefDo();
+    method public java.util.List<omapi.uuid.map.config.RefDo.UuidRefDo> getUuid_ref_do();
+  }
+
+  public static class RefDo.UuidRefDo {
+    ctor public RefDo.UuidRefDo();
+    method public omapi.uuid.map.config.RefDo.UuidRefDo.Uids getUids();
+    method public String getUuid();
+    method public void setUids(omapi.uuid.map.config.RefDo.UuidRefDo.Uids);
+    method public void setUuid(String);
+  }
+
+  public static class RefDo.UuidRefDo.Uids {
+    ctor public RefDo.UuidRefDo.Uids();
+    method public java.util.List<java.lang.Short> getUid();
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static omapi.uuid.map.config.RefDo read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+  }
+
+}
+
diff --git a/omapi/aidl/vts/functional/config/schema/last_current.txt b/omapi/aidl/vts/functional/config/schema/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/schema/last_current.txt
diff --git a/omapi/aidl/vts/functional/config/schema/last_removed.txt b/omapi/aidl/vts/functional/config/schema/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/schema/last_removed.txt
diff --git a/omapi/aidl/vts/functional/config/schema/removed.txt b/omapi/aidl/vts/functional/config/schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/omapi/aidl/vts/functional/config/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/omapi/aidl/vts/functional/omapi/Android.bp b/omapi/aidl/vts/functional/omapi/Android.bp
index c3ab8d13..c41479f 100644
--- a/omapi/aidl/vts/functional/omapi/Android.bp
+++ b/omapi/aidl/vts/functional/omapi/Android.bp
@@ -39,6 +39,11 @@
     static_libs: [
         "VtsHalHidlTargetTestBase",
         "android.se.omapi-V1-ndk",
+        "android.hardware.audio.common.test.utility",
+        "libxml2",
+    ],
+    data: [
+        ":omapi_uuid_map_config",
     ],
     cflags: [
         "-O0",
@@ -51,4 +56,5 @@
         "general-tests",
         "vts",
     ],
+    test_config: "VtsHalOmapiSeServiceV1_TargetTest.xml",
 }
diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
index 319cb7e..5303651 100644
--- a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
+++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
@@ -32,6 +32,7 @@
 #include <hidl/GtestPrinter.h>
 #include <hidl/ServiceManagement.h>
 #include <utils/String16.h>
+#include "utility/ValidateXml.h"
 
 using namespace std;
 using namespace ::testing;
@@ -176,6 +177,25 @@
         return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str()));
     }
 
+    std::optional<std::string> getUuidMappingFile() {
+        char value[PROPERTY_VALUE_MAX] = {0};
+        int len = property_get("ro.boot.product.hardware.sku", value, "config");
+        std::string uuidMappingConfigFile = UUID_MAPPING_CONFIG_PREFIX
+                + std::string(value, len)
+                + UUID_MAPPING_CONFIG_EXT;
+        std::string uuidMapConfigPath;
+        // Search in predefined folders
+        for (auto path : UUID_MAPPING_CONFIG_PATHS) {
+            uuidMapConfigPath = path + uuidMappingConfigFile;
+            auto confFile = fopen(uuidMapConfigPath.c_str(), "r");
+            if (confFile) {
+                fclose(confFile);
+                return uuidMapConfigPath;
+            }
+        }
+        return std::optional<std::string>();
+    }
+
     void SetUp() override {
         LOG(INFO) << "get OMAPI service with name:" << GetParam();
         ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str()));
@@ -300,6 +320,10 @@
 
     std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>>
         mVSReaders = {};
+
+    std::string UUID_MAPPING_CONFIG_PREFIX = "hal_uuid_map_";
+    std::string UUID_MAPPING_CONFIG_EXT = ".xml";
+    std::string UUID_MAPPING_CONFIG_PATHS[3] = {"/odm/etc/", "/vendor/etc/", "/etc/"};
 };
 
 /** Tests getReaders API */
@@ -600,6 +624,14 @@
     }
 }
 
+TEST_P(OMAPISEServiceHalTest, TestUuidMappingConfig) {
+    constexpr const char* xsd = "/data/local/tmp/omapi_uuid_map_config.xsd";
+    auto uuidMappingFile = getUuidMappingFile();
+    ASSERT_TRUE(uuidMappingFile.has_value()) << "Unable to determine UUID mapping config file path";
+    LOG(INFO) << "UUID Mapping config file: " << uuidMappingFile.value();
+    EXPECT_VALID_XML(uuidMappingFile->c_str(), xsd);
+}
+
 INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEServiceHalTest,
                          testing::ValuesIn(::android::getAidlHalInstanceNames(
                              aidl::android::se::omapi::ISecureElementService::descriptor)),
diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml
new file mode 100644
index 0000000..3ee0414
--- /dev/null
+++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Runs VtsHalOmapiSeServiceV1_TargetTest.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push"
+         value="omapi_uuid_map_config.xsd->/data/local/tmp/omapi_uuid_map_config.xsd" />
+        <option name="push"
+         value="VtsHalOmapiSeServiceV1_TargetTest->/data/local/tmp/VtsHalOmapiSeServiceV1_TargetTest" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="VtsHalOmapiSeServiceV1_TargetTest" />
+    </test>
+</configuration>
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 221a848..ca080ce 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -699,7 +699,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
     @NonNull public android.net.NetworkStats getMobileUidStats() {
         try {
             return mService.getUidStatsForTransport(TRANSPORT_CELLULAR);
@@ -723,7 +725,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
     @NonNull public android.net.NetworkStats getWifiUidStats() {
         try {
             return mService.getUidStatsForTransport(TRANSPORT_WIFI);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index 1f67f6d..1a955c4 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -16,14 +16,16 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
-import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -33,13 +35,15 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.BiConsumer;
 
 /**
- * A class representing the IP configuration of the Ethernet network.
+ * A class that manages and configures Ethernet interfaces.
  *
  * @hide
  */
@@ -54,11 +58,13 @@
     private final IEthernetServiceListener.Stub mServiceListener =
             new IEthernetServiceListener.Stub() {
                 @Override
-                public void onAvailabilityChanged(String iface, boolean isAvailable) {
+                public void onInterfaceStateChanged(String iface, int state, int role,
+                        IpConfiguration configuration) {
                     synchronized (mListeners) {
                         for (ListenerInfo li : mListeners) {
                             li.executor.execute(() ->
-                                    li.listener.onAvailabilityChanged(iface, isAvailable));
+                                    li.listener.onInterfaceStateChanged(iface, state, role,
+                                            configuration));
                         }
                     }
                 }
@@ -68,19 +74,93 @@
         @NonNull
         public final Executor executor;
         @NonNull
-        public final Listener listener;
+        public final InterfaceStateListener listener;
 
-        private ListenerInfo(@NonNull Executor executor, @NonNull Listener listener) {
+        private ListenerInfo(@NonNull Executor executor, @NonNull InterfaceStateListener listener) {
             this.executor = executor;
             this.listener = listener;
         }
     }
 
     /**
-     * A listener interface to receive notification on changes in Ethernet.
+     * The interface is absent.
      * @hide
      */
-    public interface Listener {
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int STATE_ABSENT = 0;
+
+    /**
+     * The interface is present but link is down.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int STATE_LINK_DOWN = 1;
+
+    /**
+     * The interface is present and link is up.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int STATE_LINK_UP = 2;
+
+    /** @hide */
+    @IntDef(prefix = "STATE_", value = {STATE_ABSENT, STATE_LINK_DOWN, STATE_LINK_UP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InterfaceState {}
+
+    /**
+     * The interface currently does not have any specific role.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int ROLE_NONE = 0;
+
+    /**
+     * The interface is in client mode (e.g., connected to the Internet).
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int ROLE_CLIENT = 1;
+
+    /**
+     * Ethernet interface is in server mode (e.g., providing Internet access to tethered devices).
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int ROLE_SERVER = 2;
+
+    /** @hide */
+    @IntDef(prefix = "ROLE_", value = {ROLE_NONE, ROLE_CLIENT, ROLE_SERVER})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Role {}
+
+    /**
+     * A listener that receives notifications about the state of Ethernet interfaces on the system.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public interface InterfaceStateListener {
+        /**
+         * Called when an Ethernet interface changes state.
+         *
+         * @param iface the name of the interface.
+         * @param state the current state of the interface, or {@link #STATE_ABSENT} if the
+         *              interface was removed.
+         * @param role whether the interface is in the client mode or server mode.
+         * @param configuration the current IP configuration of the interface.
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
+                @Role int role, @Nullable IpConfiguration configuration);
+    }
+
+    /**
+     * A listener interface to receive notification on changes in Ethernet.
+     * This has never been a supported API. Use {@link InterfaceStateListener} instead.
+     * @hide
+     */
+    public interface Listener extends InterfaceStateListener {
         /**
          * Called when Ethernet port's availability is changed.
          * @param iface Ethernet interface name
@@ -89,6 +169,13 @@
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         void onAvailabilityChanged(String iface, boolean isAvailable);
+
+        /** Default implementation for backwards compatibility. Only calls the legacy listener. */
+        default void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
+                @Role int role, @Nullable IpConfiguration configuration) {
+            onAvailabilityChanged(iface, (state >= STATE_LINK_UP));
+        }
+
     }
 
     /**
@@ -121,7 +208,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void setConfiguration(String iface, IpConfiguration config) {
+    public void setConfiguration(@NonNull String iface, @NonNull IpConfiguration config) {
         try {
             mService.setConfiguration(iface, config);
         } catch (RemoteException e) {
@@ -155,9 +242,8 @@
 
     /**
      * Adds a listener.
+     * This has never been a supported API. Use {@link #addInterfaceStateListener} instead.
      *
-     * Consider using {@link #addListener(Listener, Executor)} instead: this method uses a default
-     * executor that may have higher latency than a provided executor.
      * @param listener A {@link Listener} to add.
      * @throws IllegalArgumentException If the listener is null.
      * @hide
@@ -169,6 +255,8 @@
 
     /**
      * Adds a listener.
+     * This has never been a supported API. Use {@link #addInterfaceStateListener} instead.
+     *
      * @param listener A {@link Listener} to add.
      * @param executor Executor to run callbacks on.
      * @throws IllegalArgumentException If the listener or executor is null.
@@ -176,6 +264,28 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void addListener(@NonNull Listener listener, @NonNull Executor executor) {
+        addInterfaceStateListener(executor, listener);
+    }
+
+    /**
+     * Listen to changes in the state of Ethernet interfaces.
+     *
+     * Adds a listener to receive notification for any state change of all existing Ethernet
+     * interfaces.
+     * <p>{@link Listener#onInterfaceStateChanged} will be triggered immediately for all
+     * existing interfaces upon adding a listener. The same method will be called on the
+     * listener every time any of the interface changes state. In particular, if an
+     * interface is removed, it will be called with state {@link #STATE_ABSENT}.
+     * <p>Use {@link #removeInterfaceStateListener} with the same object to stop listening.
+     *
+     * @param executor Executor to run callbacks on.
+     * @param listener A {@link Listener} to add.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    @SystemApi(client = MODULE_LIBRARIES)
+    public void addInterfaceStateListener(@NonNull Executor executor,
+            @NonNull InterfaceStateListener listener) {
         if (listener == null || executor == null) {
             throw new NullPointerException("listener and executor must not be null");
         }
@@ -206,15 +316,13 @@
 
     /**
      * Removes a listener.
+     *
      * @param listener A {@link Listener} to remove.
-     * @throws IllegalArgumentException If the listener is null.
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void removeListener(@NonNull Listener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
+    @SystemApi(client = MODULE_LIBRARIES)
+    public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) {
+        Objects.requireNonNull(listener);
         synchronized (mListeners) {
             mListeners.removeIf(l -> l.listener == listener);
             if (mListeners.isEmpty()) {
@@ -228,12 +336,26 @@
     }
 
     /**
+     * Removes a listener.
+     * This has never been a supported API. Use {@link #removeInterfaceStateListener} instead.
+     * @param listener A {@link Listener} to remove.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void removeListener(@NonNull Listener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        removeInterfaceStateListener(listener);
+    }
+
+    /**
      * Whether to treat interfaces created by {@link TestNetworkManager#createTapInterface}
      * as Ethernet interfaces. The effects of this method apply to any test interfaces that are
      * already present on the system.
      * @hide
      */
-    @TestApi
+    @SystemApi(client = MODULE_LIBRARIES)
     public void setIncludeTestInterfaces(boolean include) {
         try {
             mService.setIncludeTestInterfaces(include);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
index 782fa19..6d2ba03 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
@@ -16,8 +16,11 @@
 
 package android.net;
 
+import android.net.IpConfiguration;
+
 /** @hide */
 oneway interface IEthernetServiceListener
 {
-    void onAvailabilityChanged(String iface, boolean isAvailable);
+    void onInterfaceStateChanged(String iface, int state, int role,
+            in IpConfiguration configuration);
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index 4ebaf2b..a48f94b 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -20,6 +20,7 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -86,6 +87,7 @@
 
     final int mType;
     final int mRatType;
+    final int mSubId;
     final String mSubscriberId;
     final String mWifiNetworkKey;
     final boolean mRoaming;
@@ -96,7 +98,7 @@
     /** @hide */
     public NetworkIdentity(
             int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey,
-            boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged) {
+            boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId) {
         mType = type;
         mRatType = ratType;
         mSubscriberId = subscriberId;
@@ -105,12 +107,13 @@
         mMetered = metered;
         mDefaultNetwork = defaultNetwork;
         mOemManaged = oemManaged;
+        mSubId = subId;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered,
-                mDefaultNetwork, mOemManaged);
+                mDefaultNetwork, mOemManaged, mSubId);
     }
 
     @Override
@@ -122,7 +125,8 @@
                     && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey)
                     && mMetered == ident.mMetered
                     && mDefaultNetwork == ident.mDefaultNetwork
-                    && mOemManaged == ident.mOemManaged;
+                    && mOemManaged == ident.mOemManaged
+                    && mSubId == ident.mSubId;
         }
         return false;
     }
@@ -150,6 +154,7 @@
         builder.append(", metered=").append(mMetered);
         builder.append(", defaultNetwork=").append(mDefaultNetwork);
         builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
+        builder.append(", subId=").append(mSubId);
         return builder.append("}").toString();
     }
 
@@ -256,6 +261,11 @@
         return mOemManaged;
     }
 
+    /** Get the SubId of this instance. */
+    public int getSubId() {
+        return mSubId;
+    }
+
     /**
      * Assemble a {@link NetworkIdentity} from the passed arguments.
      *
@@ -276,7 +286,8 @@
     public static NetworkIdentity buildNetworkIdentity(Context context,
             @NonNull NetworkStateSnapshot snapshot, boolean defaultNetwork, int ratType) {
         final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
-                .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork);
+                .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork)
+                .setSubId(snapshot.getSubId());
         if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) {
             builder.setRatType(ratType);
         }
@@ -325,6 +336,9 @@
         if (res == 0) {
             res = Integer.compare(left.mOemManaged, right.mOemManaged);
         }
+        if (res == 0) {
+            res = Integer.compare(left.mSubId, right.mSubId);
+        }
         return res;
     }
 
@@ -345,6 +359,7 @@
         private boolean mMetered;
         private boolean mDefaultNetwork;
         private int mOemManaged;
+        private int mSubId;
 
         /**
          * Creates a new Builder.
@@ -359,6 +374,7 @@
             mMetered = false;
             mDefaultNetwork = false;
             mOemManaged = NetworkTemplate.OEM_MANAGED_NO;
+            mSubId = INVALID_SUBSCRIPTION_ID;
         }
 
         /**
@@ -537,6 +553,19 @@
             return this;
         }
 
+        /**
+         * Set the Subscription Id.
+         *
+         * @param subId the Subscription Id of the network. Or INVALID_SUBSCRIPTION_ID if not
+         *              applicable.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setSubId(int subId) {
+            mSubId = subId;
+            return this;
+        }
+
         private void ensureValidParameters() {
             // Assert non-mobile network cannot have a ratType.
             if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) {
@@ -559,7 +588,7 @@
         public NetworkIdentity build() {
             ensureValidParameters();
             return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey,
-                    mRoaming, mMetered, mDefaultNetwork, mOemManaged);
+                    mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId);
         }
     }
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
index 2236d70..56461ba 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.annotation.NonNull;
 import android.service.NetworkIdentitySetProto;
@@ -42,6 +43,7 @@
     private static final int VERSION_ADD_METERED = 4;
     private static final int VERSION_ADD_DEFAULT_NETWORK = 5;
     private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
+    private static final int VERSION_ADD_SUB_ID = 7;
 
     /**
      * Construct a {@link NetworkIdentitySet} object.
@@ -103,8 +105,15 @@
                 oemNetCapabilities = NetworkIdentity.OEM_NONE;
             }
 
+            final int subId;
+            if (version >= VERSION_ADD_SUB_ID) {
+                subId = in.readInt();
+            } else {
+                subId = INVALID_SUBSCRIPTION_ID;
+            }
+
             add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered,
-                    defaultNetwork, oemNetCapabilities));
+                    defaultNetwork, oemNetCapabilities, subId));
         }
     }
 
@@ -113,7 +122,7 @@
      * @hide
      */
     public void writeToStream(DataOutput out) throws IOException {
-        out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK);
+        out.writeInt(VERSION_ADD_SUB_ID);
         out.writeInt(size());
         for (NetworkIdentity ident : this) {
             out.writeInt(ident.getType());
@@ -124,6 +133,7 @@
             out.writeBoolean(ident.isMetered());
             out.writeBoolean(ident.isDefaultNetwork());
             out.writeInt(ident.getOemManaged());
+            out.writeInt(ident.getSubId());
         }
     }
 
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
index d577aa8..c018e91 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
@@ -17,6 +17,8 @@
 package android.net;
 
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -98,12 +100,29 @@
         return mLinkProperties;
     }
 
-    /** Get the Subscriber Id of the network associated with this snapshot. */
+    /**
+     * Get the Subscriber Id of the network associated with this snapshot.
+     * @deprecated Please use #getSubId, which doesn't return personally identifiable
+     * information.
+     */
+    @Deprecated
     @Nullable
     public String getSubscriberId() {
         return mSubscriberId;
     }
 
+    /** Get the subId of the network associated with this snapshot. */
+    public int getSubId() {
+        if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            final NetworkSpecifier spec = mNetworkCapabilities.getNetworkSpecifier();
+            if (spec instanceof TelephonyNetworkSpecifier) {
+                return ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
+            }
+        }
+        return INVALID_SUBSCRIPTION_ID;
+    }
+
+
     /**
      * Get the legacy type of the network associated with this snapshot.
      * @return the legacy network type. See {@code ConnectivityManager#TYPE_*}.
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index e366ca1..e8b3d4c 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -1067,7 +1067,7 @@
 
     @Override
     public NetworkStats getUidStatsForTransport(int transport) {
-        enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             final String[] relevantIfaces =
                     transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces;
@@ -1484,10 +1484,15 @@
                         NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.isMetered()) {
 
                     // Copy the identify from IMS one but mark it as metered.
-                    NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(),
-                            ident.getRatType(), ident.getSubscriberId(), ident.getWifiNetworkKey(),
-                            ident.isRoaming(), true /* metered */,
-                            true /* onDefaultNetwork */, ident.getOemManaged());
+                    NetworkIdentity vtIdent = new NetworkIdentity.Builder()
+                            .setType(ident.getType())
+                            .setRatType(ident.getRatType())
+                            .setSubscriberId(ident.getSubscriberId())
+                            .setWifiNetworkKey(ident.getWifiNetworkKey())
+                            .setRoaming(ident.isRoaming()).setMetered(true)
+                            .setDefaultNetwork(true)
+                            .setOemManaged(ident.getOemManaged())
+                            .setSubId(ident.getSubId()).build();
                     final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot);
                     findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent);
                     findOrCreateNetworkIdentitySet(mActiveUidIfaces, ifaceVt).add(vtIdent);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index f7b2974..17db45d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -138,8 +138,6 @@
             return true;
         }
         if (mDisabledByAppOps) {
-            Preconditions.checkState(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU,
-                    "Build SDK version needs >= T");
             RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
                     uid);
             return true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 5c05a1b..342189d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -61,6 +61,7 @@
 
             final TypedValue restrictedSwitchSummary = attributes.peekValue(
                     R.styleable.RestrictedSwitchPreference_restrictedSwitchSummary);
+            attributes.recycle();
             if (restrictedSwitchSummary != null
                     && restrictedSwitchSummary.type == TypedValue.TYPE_STRING) {
                 if (restrictedSwitchSummary.resourceId != 0) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index d179b82..4e06ed7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -26,8 +26,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -35,21 +33,14 @@
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.text.TextUtils;
-import android.util.AttributeSet;
 import android.util.Log;
-import android.util.Xml;
 
 import com.android.settingslib.R;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
@@ -185,15 +176,18 @@
             dreamInfo.componentName = componentName;
             dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
 
-            final DreamMetadata dreamMetadata = getDreamMetadata(pm, resolveInfo);
-            dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity;
-            dreamInfo.previewImage = dreamMetadata.mPreviewImage;
+            final DreamService.DreamMetadata dreamMetadata = DreamService.getDreamMetadata(mContext,
+                    resolveInfo.serviceInfo);
+            if (dreamMetadata != null) {
+                dreamInfo.settingsComponentName = dreamMetadata.settingsActivity;
+                dreamInfo.previewImage = dreamMetadata.previewImage;
+            }
             if (dreamInfo.previewImage == null) {
                 dreamInfo.previewImage = mDreamPreviewDefault;
             }
             dreamInfos.add(dreamInfo);
         }
-        Collections.sort(dreamInfos, mComparator);
+        dreamInfos.sort(mComparator);
         return dreamInfos;
     }
 
@@ -454,21 +448,26 @@
         uiContext.startActivity(intent);
     }
 
-    public void preview(DreamInfo dreamInfo) {
-        logd("preview(%s)", dreamInfo);
-        if (mDreamManager == null || dreamInfo == null || dreamInfo.componentName == null)
+    /**
+     * Preview a dream, given the component name.
+     */
+    public void preview(ComponentName componentName) {
+        logd("preview(%s)", componentName);
+        if (mDreamManager == null || componentName == null) {
             return;
+        }
         try {
-            mDreamManager.testDream(mContext.getUserId(), dreamInfo.componentName);
+            mDreamManager.testDream(mContext.getUserId(), componentName);
         } catch (RemoteException e) {
-            Log.w(TAG, "Failed to preview " + dreamInfo, e);
+            Log.w(TAG, "Failed to preview " + componentName, e);
         }
     }
 
     public void startDreaming() {
         logd("startDreaming()");
-        if (mDreamManager == null)
+        if (mDreamManager == null) {
             return;
+        }
         try {
             mDreamManager.dream();
         } catch (RemoteException e) {
@@ -483,78 +482,6 @@
         return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
     }
 
-    private static final class DreamMetadata {
-        @Nullable
-        Drawable mPreviewImage;
-        @Nullable
-        ComponentName mSettingsActivity;
-    }
-
-    @Nullable
-    private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) {
-        if (serviceInfo == null || serviceInfo.metaData == null) {
-            return null;
-        }
-        try (XmlResourceParser parser =
-                     serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) {
-            if (parser == null) {
-                Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data");
-                return null;
-            }
-            Resources res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
-            AttributeSet attrs = Xml.asAttributeSet(parser);
-            while (true) {
-                final int type = parser.next();
-                if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) {
-                    break;
-                }
-            }
-            String nodeName = parser.getName();
-            if (!"dream".equals(nodeName)) {
-                Log.w(TAG, "Meta-data does not start with dream tag");
-                return null;
-            }
-            return res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
-        } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
-            Log.w(TAG, "Error parsing : " + serviceInfo.packageName, e);
-            return null;
-        }
-    }
-
-    private static ComponentName convertToComponentName(String flattenedString,
-            ServiceInfo serviceInfo) {
-        if (flattenedString == null) return null;
-
-        if (flattenedString.indexOf('/') < 0) {
-            flattenedString = serviceInfo.packageName + "/" + flattenedString;
-        }
-
-        ComponentName cn = ComponentName.unflattenFromString(flattenedString);
-
-        if (cn == null) return null;
-        if (!cn.getPackageName().equals(serviceInfo.packageName)) {
-            Log.w(TAG,
-                    "Inconsistent package name in component: " + cn.getPackageName()
-                            + ", should be: " + serviceInfo.packageName);
-            return null;
-        }
-
-        return cn;
-    }
-
-    private static DreamMetadata getDreamMetadata(PackageManager pm, ResolveInfo resolveInfo) {
-        DreamMetadata result = new DreamMetadata();
-        if (resolveInfo == null) return result;
-        TypedArray rawMetadata = readMetadata(pm, resolveInfo.serviceInfo);
-        if (rawMetadata == null) return result;
-        result.mSettingsActivity = convertToComponentName(rawMetadata.getString(
-                com.android.internal.R.styleable.Dream_settingsActivity), resolveInfo.serviceInfo);
-        result.mPreviewImage = rawMetadata.getDrawable(
-                com.android.internal.R.styleable.Dream_previewImage);
-        rawMetadata.recycle();
-        return result;
-    }
-
     private static void logd(String msg, Object... args) {
         if (DEBUG) {
             Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 26e3e04..1f4cafce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -32,7 +32,6 @@
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.Switch;
 import android.widget.Toast;
 
@@ -156,14 +155,10 @@
         final int iconSize = getContext().getResources().getDimensionPixelSize(
                 R.dimen.secondary_app_icon_size);
         if (icon != null && iconSize > 0) {
-            if (isTv()) {
-                ViewGroup.LayoutParams params = icon.getLayoutParams();
-                params.height = iconSize;
-                params.width = iconSize;
-                icon.setLayoutParams(params);
-            } else {
-                icon.setLayoutParams(new LinearLayout.LayoutParams(iconSize, iconSize));
-            }
+            ViewGroup.LayoutParams params = icon.getLayoutParams();
+            params.height = iconSize;
+            params.width = iconSize;
+            icon.setLayoutParams(params);
         }
     }
 
@@ -226,8 +221,8 @@
             setSwitchEnabled(false);
         } else if (!mIsAllowedByOrganization) {
             EnforcedAdmin admin =
-                    RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(getContext(),
-                            mImi.getPackageName(), UserHandle.myUserId());
+                    RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(
+                            getContext(), mImi.getPackageName(), mUserId);
             setDisabledByAdmin(admin);
         } else {
             setEnabled(true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
index b4c95e6..2c2be03 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
@@ -39,6 +39,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
 import java.util.zip.GZIPInputStream;
 
 /**
@@ -54,6 +56,7 @@
     private static final String TAG_FILE_NAME = "file-name";
     private static final String TAG_FILE_CONTENT = "file-content";
     private static final String ATTR_CONTENT_ID = "contentId";
+    private static final String ATTR_LIBRARY_NAME = "lib";
 
     private static final String HTML_HEAD_STRING =
             "<html><head>\n"
@@ -67,8 +70,12 @@
             + "</style>\n"
             + "</head>"
             + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n"
-            + "<div class=\"toc\">\n"
-            + "<ul>";
+            + "<div class=\"toc\">\n";
+    private static final String LIBRARY_HEAD_STRING =
+            "<strong>Libraries</strong>\n<ul class=\"libraries\">";
+    private static final String LIBRARY_TAIL_STRING = "</ul>\n<strong>Files</strong>";
+
+    private static final String FILES_HEAD_STRING = "<ul class=\"files\">";
 
     private static final String HTML_MIDDLE_STRING =
             "</ul>\n"
@@ -81,12 +88,14 @@
     private final List<File> mXmlFiles;
 
     /*
-     * A map from a file name to a content id (MD5 sum of file content) for its license.
-     * For example, "/system/priv-app/TeleService/TeleService.apk" maps to
+     * A map from a file name to a library name (may be empty) to a content id (MD5 sum of file
+     * content) for its license.
+     * For example, "/system/priv-app/TeleService/TeleService.apk" maps to "service/Telephony" to
      * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum
      * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2.
      */
-    private final Map<String, Set<String>> mFileNameToContentIdMap = new HashMap();
+    private final Map<String, Map<String, Set<String>>> mFileNameToLibraryToContentIdMap =
+            new HashMap();
 
     /*
      * A map from a content id (MD5 sum of file content) to a license file content.
@@ -98,7 +107,7 @@
 
     static class ContentIdAndFileNames {
         final String mContentId;
-        final List<String> mFileNameList = new ArrayList();
+        final Map<String, List<String>> mLibraryToFileNameMap = new TreeMap();
 
         ContentIdAndFileNames(String contentId) {
             mContentId = contentId;
@@ -120,7 +129,7 @@
             parse(xmlFile);
         }
 
-        if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) {
+        if (mFileNameToLibraryToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) {
             return false;
         }
 
@@ -128,7 +137,7 @@
         try {
             writer = new PrintWriter(outputFile);
 
-            generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer,
+            generateHtml(mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap, writer,
                 noticeHeader);
 
             writer.flush();
@@ -157,7 +166,7 @@
                 in = new FileReader(xmlFile);
             }
 
-            parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap);
+            parse(in, mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap);
 
             in.close();
         } catch (XmlPullParserException | IOException e) {
@@ -180,7 +189,8 @@
      *
      *     <licenses>
      *     <file-name contentId="content_id_of_license1">file1</file-name>
-     *     <file-name contentId="content_id_of_license2">file2</file-name>
+     *     <file-name contentId="content_id_of_license2" lib="name of library">file2</file-name>
+     *     <file-name contentId="content_id_of_license2" lib="another library">file2</file-name>
      *     ...
      *     <file-content contentId="content_id_of_license1">license1 file contents</file-content>
      *     <file-content contentId="content_id_of_license2">license2 file contents</file-content>
@@ -188,10 +198,12 @@
      *     </licenses>
      */
     @VisibleForTesting
-    static void parse(InputStreamReader in, Map<String, Set<String>> outFileNameToContentIdMap,
+    static void parse(InputStreamReader in,
+            Map<String, Map<String, Set<String>>> outFileNameToLibraryToContentIdMap,
             Map<String, String> outContentIdToFileContentMap)
                     throws XmlPullParserException, IOException {
-        Map<String, Set<String>> fileNameToContentIdMap = new HashMap<String, Set<String>>();
+        Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap =
+                new HashMap<String, Map<String, Set<String>>>();
         Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
 
         XmlPullParser parser = Xml.newPullParser();
@@ -205,12 +217,15 @@
             if (state == XmlPullParser.START_TAG) {
                 if (TAG_FILE_NAME.equals(parser.getName())) {
                     String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID);
+                    String libraryName = parser.getAttributeValue("", ATTR_LIBRARY_NAME);
                     if (!TextUtils.isEmpty(contentId)) {
                         String fileName = readText(parser).trim();
                         if (!TextUtils.isEmpty(fileName)) {
-                            Set<String> contentIds =
-                                    fileNameToContentIdMap.computeIfAbsent(
-                                            fileName, k -> new HashSet<>());
+                            Map<String, Set<String>> libs =
+                                    fileNameToLibraryToContentIdMap.computeIfAbsent(
+                                            fileName, k -> new HashMap<>());
+                            Set<String> contentIds = libs.computeIfAbsent(
+                                            libraryName, k -> new HashSet<>());
                             contentIds.add(contentId);
                         }
                     }
@@ -229,11 +244,17 @@
 
             state = parser.next();
         }
-        for (Map.Entry<String, Set<String>> entry : fileNameToContentIdMap.entrySet()) {
-            outFileNameToContentIdMap.merge(
-                    entry.getKey(), entry.getValue(), (s1, s2) -> {
-                        s1.addAll(s2);
-                        return s1;
+        for (Map.Entry<String, Map<String, Set<String>>> mapEntry :
+                fileNameToLibraryToContentIdMap.entrySet()) {
+            outFileNameToLibraryToContentIdMap.merge(
+                    mapEntry.getKey(), mapEntry.getValue(), (m1, m2) -> {
+                        for (Map.Entry<String, Set<String>> entry : m2.entrySet()) {
+                            m1.merge(entry.getKey(), entry.getValue(), (s1, s2) -> {
+                                s1.addAll(s2);
+                                return s1;
+                            });
+                        }
+                        return m1;
                     });
         }
         outContentIdToFileContentMap.putAll(contentIdToFileContentMap);
@@ -251,13 +272,28 @@
     }
 
     @VisibleForTesting
-    static void generateHtml(Map<String, Set<String>> fileNameToContentIdMap,
+    static void generateHtml(Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap,
             Map<String, String> contentIdToFileContentMap, PrintWriter writer,
             String noticeHeader) {
         List<String> fileNameList = new ArrayList();
-        fileNameList.addAll(fileNameToContentIdMap.keySet());
+        fileNameList.addAll(fileNameToLibraryToContentIdMap.keySet());
         Collections.sort(fileNameList);
 
+        SortedMap<String, Set<String>> libraryToContentIdMap = new TreeMap();
+        for (Map<String, Set<String>> libraryToContentValue :
+                fileNameToLibraryToContentIdMap.values()) {
+            for (Map.Entry<String, Set<String>> entry : libraryToContentValue.entrySet()) {
+                if (TextUtils.isEmpty(entry.getKey())) {
+                    continue;
+                }
+                libraryToContentIdMap.merge(
+                        entry.getKey(), entry.getValue(), (s1, s2) -> {
+                            s1.addAll(s2);
+                            return s1;
+                        });
+            }
+        }
+
         writer.println(HTML_HEAD_STRING);
 
         if (!TextUtils.isEmpty(noticeHeader)) {
@@ -268,21 +304,56 @@
         Map<String, Integer> contentIdToOrderMap = new HashMap();
         List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList();
 
+        if (!libraryToContentIdMap.isEmpty()) {
+            writer.println(LIBRARY_HEAD_STRING);
+            for (Map.Entry<String, Set<String>> entry: libraryToContentIdMap.entrySet()) {
+                String libraryName = entry.getKey();
+                for (String contentId : entry.getValue()) {
+                    // Assigns an id to a newly referred license file content.
+                    if (!contentIdToOrderMap.containsKey(contentId)) {
+                        contentIdToOrderMap.put(contentId, count);
+
+                        // An index in contentIdAndFileNamesList is the order of each element.
+                        contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
+                        count++;
+                    }
+                    int id = contentIdToOrderMap.get(contentId);
+                    writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, libraryName);
+                }
+            }
+            writer.println(LIBRARY_TAIL_STRING);
+        }
+
         // Prints all the file list with a link to its license file content.
         for (String fileName : fileNameList) {
-            for (String contentId : fileNameToContentIdMap.get(fileName)) {
-                // Assigns an id to a newly referred license file content.
-                if (!contentIdToOrderMap.containsKey(contentId)) {
-                    contentIdToOrderMap.put(contentId, count);
-
-                    // An index in contentIdAndFileNamesList is the order of each element.
-                    contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
-                    count++;
+            for (Map.Entry<String, Set<String>> libToContentId :
+                    fileNameToLibraryToContentIdMap.get(fileName).entrySet()) {
+                String libraryName = libToContentId.getKey();
+                if (libraryName == null) {
+                    libraryName = "";
                 }
+                for (String contentId : libToContentId.getValue()) {
+                    // Assigns an id to a newly referred license file content.
+                    if (!contentIdToOrderMap.containsKey(contentId)) {
+                        contentIdToOrderMap.put(contentId, count);
 
-                int id = contentIdToOrderMap.get(contentId);
-                contentIdAndFileNamesList.get(id).mFileNameList.add(fileName);
-                writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+                        // An index in contentIdAndFileNamesList is the order of each element.
+                        contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
+                        count++;
+                    }
+
+                    int id = contentIdToOrderMap.get(contentId);
+                    ContentIdAndFileNames elem = contentIdAndFileNamesList.get(id);
+                    List<String> files = elem.mLibraryToFileNameMap.computeIfAbsent(
+                            libraryName, k -> new ArrayList());
+                    files.add(fileName);
+                    if (TextUtils.isEmpty(libraryName)) {
+                        writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+                    } else {
+                        writer.format("<li><a href=\"#id%d\">%s - %s</a></li>\n",
+                                id, fileName, libraryName);
+                    }
+                }
             }
         }
 
@@ -292,19 +363,27 @@
         // Prints all contents of the license files in order of id.
         for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
             writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count);
-            writer.println("<div class=\"label\">Notices for file(s):</div>");
-            writer.println("<div class=\"file-list\">");
-            for (String fileName : contentIdAndFileNames.mFileNameList) {
-                writer.format("%s <br/>\n", fileName);
+            for (Map.Entry<String, List<String>> libraryFiles :
+                    contentIdAndFileNames.mLibraryToFileNameMap.entrySet()) {
+                String libraryName = libraryFiles.getKey();
+                if (TextUtils.isEmpty(libraryName)) {
+                    writer.println("<div class=\"label\">Notices for file(s):</div>");
+                } else {
+                    writer.format("<div class=\"label\"><strong>%s</strong> used by:</div>\n",
+                            libraryName);
+                }
+                writer.println("<div class=\"file-list\">");
+                for (String fileName : libraryFiles.getValue()) {
+                    writer.format("%s <br/>\n", fileName);
+                }
+                writer.println("</div><!-- file-list -->");
+                count++;
             }
-            writer.println("</div><!-- file-list -->");
             writer.println("<pre class=\"license-text\">");
             writer.println(contentIdToFileContentMap.get(
                     contentIdAndFileNames.mContentId));
             writer.println("</pre><!-- license-text -->");
             writer.println("</td></tr><!-- same-license -->");
-
-            count++;
         }
 
         writer.println(HTML_REAR_STRING);
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java b/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java
index 1805f1a..42e3af0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java
@@ -46,12 +46,12 @@
     public final String className;
 
     /**
-     * The {@link android.support.v7.preference.Preference#getTitle()} value.
+     * The {@link androidx.preference.Preference#getTitle()} value.
      */
     public final String title;
 
     /**
-     * The {@link android.support.v7.preference.Preference#getIcon()} value.
+     * The {@link androidx.preference.Preference#getIcon()} value.
      */
     public final int iconId;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 360361b..dd7db21 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
 
@@ -34,11 +35,13 @@
     private static final String TAG = "BluetoothMediaDevice";
 
     private CachedBluetoothDevice mCachedDevice;
+    private final AudioManager mAudioManager;
 
     BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
             MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
         super(context, routerManager, info, packageName);
         mCachedDevice = device;
+        mAudioManager = context.getSystemService(AudioManager.class);
         initDeviceRecord();
     }
 
@@ -98,6 +101,12 @@
     }
 
     @Override
+    public boolean isMutingExpectedDevice() {
+        return mAudioManager.getMutingExpectedDevice() != null && mCachedDevice.getAddress().equals(
+                mAudioManager.getMutingExpectedDevice().getAddress());
+    }
+
+    @Override
     public boolean isConnected() {
         return mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
                 && mCachedDevice.isConnected();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
new file mode 100644
index 0000000..6c0eab3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -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.media;
+
+import android.annotation.DrawableRes;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceInfo;
+import android.media.MediaRoute2Info;
+
+import com.android.settingslib.R;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** A util class to get the appropriate icon for different device types. */
+public class DeviceIconUtil {
+    // A map from a @AudioDeviceInfo.AudioDeviceType to full device information.
+    private final Map<Integer, Device> mAudioDeviceTypeToIconMap = new HashMap<>();
+    // A map from a @MediaRoute2Info.Type to full device information.
+    private final Map<Integer, Device> mMediaRouteTypeToIconMap = new HashMap<>();
+    // A default icon to use if the type is not present in the map.
+    @DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone;
+
+    public DeviceIconUtil() {
+        List<Device> deviceList = Arrays.asList(
+                new Device(
+                    AudioDeviceInfo.TYPE_USB_DEVICE,
+                    MediaRoute2Info.TYPE_USB_DEVICE,
+                    R.drawable.ic_headphone),
+                new Device(
+                    AudioDeviceInfo.TYPE_USB_HEADSET,
+                    MediaRoute2Info.TYPE_USB_HEADSET,
+                    R.drawable.ic_headphone),
+                new Device(
+                    AudioDeviceInfo.TYPE_USB_ACCESSORY,
+                    MediaRoute2Info.TYPE_USB_ACCESSORY,
+                    R.drawable.ic_headphone),
+                new Device(
+                    AudioDeviceInfo.TYPE_DOCK,
+                    MediaRoute2Info.TYPE_DOCK,
+                    R.drawable.ic_headphone),
+                new Device(
+                    AudioDeviceInfo.TYPE_HDMI,
+                    MediaRoute2Info.TYPE_HDMI,
+                    R.drawable.ic_headphone),
+                new Device(
+                    AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                    MediaRoute2Info.TYPE_WIRED_HEADSET,
+                    R.drawable.ic_headphone),
+                new Device(
+                    AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+                    MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+                    R.drawable.ic_headphone),
+                new Device(
+                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                    MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
+                    R.drawable.ic_smartphone));
+        for (int i = 0; i < deviceList.size(); i++) {
+            Device device = deviceList.get(i);
+            mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device);
+            mMediaRouteTypeToIconMap.put(device.mMediaRouteType, device);
+        }
+    }
+
+    /** Returns a drawable for an icon representing the given audioDeviceType. */
+    public Drawable getIconFromAudioDeviceType(
+            @AudioDeviceInfo.AudioDeviceType int audioDeviceType, Context context) {
+        return context.getDrawable(getIconResIdFromAudioDeviceType(audioDeviceType));
+    }
+
+    /** Returns a drawable res ID for an icon representing the given audioDeviceType. */
+    @DrawableRes
+    public int getIconResIdFromAudioDeviceType(
+            @AudioDeviceInfo.AudioDeviceType int audioDeviceType) {
+        if (mAudioDeviceTypeToIconMap.containsKey(audioDeviceType)) {
+            return mAudioDeviceTypeToIconMap.get(audioDeviceType).mIconDrawableRes;
+        }
+        return DEFAULT_ICON;
+    }
+
+    /** Returns a drawable res ID for an icon representing the given mediaRouteType. */
+    @DrawableRes
+    public int getIconResIdFromMediaRouteType(
+            @MediaRoute2Info.Type int mediaRouteType) {
+        if (mMediaRouteTypeToIconMap.containsKey(mediaRouteType)) {
+            return mMediaRouteTypeToIconMap.get(mediaRouteType).mIconDrawableRes;
+        }
+        return DEFAULT_ICON;
+    }
+
+    private static class Device {
+        @AudioDeviceInfo.AudioDeviceType
+        private final int mAudioDeviceType;
+
+        @MediaRoute2Info.Type
+        private final int mMediaRouteType;
+
+        @DrawableRes
+        private final int mIconDrawableRes;
+
+        Device(@AudioDeviceInfo.AudioDeviceType int audioDeviceType,
+                @MediaRoute2Info.Type int mediaRouteType,
+                @DrawableRes int iconDrawableRes) {
+            mAudioDeviceType = audioDeviceType;
+            mMediaRouteType = mediaRouteType;
+            mIconDrawableRes = iconDrawableRes;
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index b5facf3..31d5921 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -21,6 +21,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.media.RoutingSessionInfo;
 import android.os.Build;
 import android.text.TextUtils;
@@ -227,6 +228,18 @@
     }
 
     /**
+     * Dispatch a change in the about-to-connect device. See
+     * {@link DeviceCallback#onAboutToConnectDeviceChanged} for more information.
+     */
+    public void dispatchAboutToConnectDeviceChanged(
+            @Nullable String deviceName,
+            @Nullable Drawable deviceIcon) {
+        for (DeviceCallback callback : getCallbacks()) {
+            callback.onAboutToConnectDeviceChanged(deviceName, deviceIcon);
+        }
+    }
+
+    /**
      * Stop scan MediaDevice
      */
     public void stopScan() {
@@ -674,6 +687,21 @@
          * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
          */
         default void onRequestFailed(int reason){};
+
+        /**
+         * Callback for notifying that we have a new about-to-connect device.
+         *
+         * An about-to-connect device is a device that is not yet connected but is expected to
+         * connect imminently and should be displayed as the current device in the media player.
+         * See [AudioManager.muteAwaitConnection] for more details.
+         *
+         * @param deviceName the name of the device (displayed to the user).
+         * @param deviceIcon the icon that should be used with the device.
+         */
+        default void onAboutToConnectDeviceChanged(
+                @Nullable String deviceName,
+                @Nullable Drawable deviceIcon
+        ) {}
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 970abff..c759962 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -320,6 +320,13 @@
         }
 
         if (mType == another.mType) {
+            // Check device is muting expected device
+            if (isMutingExpectedDevice()) {
+                return -1;
+            } else if (another.isMutingExpectedDevice()) {
+                return 1;
+            }
+
             // Check fast pair device
             if (isFastPairDevice()) {
                 return -1;
@@ -392,6 +399,14 @@
         return false;
     }
 
+    /**
+     * Check if it is muting expected device
+     * @return {@code true} if it is muting expected device, otherwise return {@code false}
+     */
+    protected boolean isMutingExpectedDevice() {
+        return false;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof MediaDevice)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index c16ecb5..921c245 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -47,10 +47,12 @@
 
     private String mSummary = "";
 
+    private final DeviceIconUtil mDeviceIconUtil;
+
     PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
             String packageName) {
         super(context, routerManager, info, packageName);
-
+        mDeviceIconUtil = new DeviceIconUtil();
         initDeviceRecord();
     }
 
@@ -94,23 +96,7 @@
 
     @VisibleForTesting
     int getDrawableResId() {
-        int resId;
-        switch (mRouteInfo.getType()) {
-            case TYPE_USB_DEVICE:
-            case TYPE_USB_HEADSET:
-            case TYPE_USB_ACCESSORY:
-            case TYPE_DOCK:
-            case TYPE_HDMI:
-            case TYPE_WIRED_HEADSET:
-            case TYPE_WIRED_HEADPHONES:
-                resId = R.drawable.ic_headphone;
-                break;
-            case TYPE_BUILTIN_SPEAKER:
-            default:
-                resId = R.drawable.ic_smartphone;
-                break;
-        }
-        return resId;
+        return mDeviceIconUtil.getIconResIdFromMediaRouteType(mRouteInfo.getType());
     }
 
     @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index e87461f..e348865 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -36,7 +36,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class LicenseHtmlGeneratorFromXmlTest {
-    private static final String VALILD_XML_STRING =
+    private static final String VALID_OLD_XML_STRING =
             "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
             + "<licenses>\n"
             + "<file-name contentId=\"0\">/file0</file-name>\n"
@@ -44,7 +44,15 @@
             + "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n"
             + "</licenses>";
 
-    private static final String INVALILD_XML_STRING =
+    private static final String VALID_NEW_XML_STRING =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+            + "<licenses>\n"
+            + "<file-name contentId=\"0\" lib=\"libA\">/file0</file-name>\n"
+            + "<file-name contentId=\"0\" lib=\"libB\">/file1</file-name>\n"
+            + "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n"
+            + "</licenses>";
+
+    private static final String INVALID_XML_STRING =
             "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
             + "<licenses2>\n"
             + "<file-name contentId=\"0\">/file0</file-name>\n"
@@ -64,13 +72,13 @@
             + "</style>\n"
             + "</head>"
             + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n"
-            + "<div class=\"toc\">\n"
-            + "<ul>\n";
+            + "<div class=\"toc\">\n";
 
     private static final String HTML_CUSTOM_HEADING = "Custom heading";
 
-    private static final String HTML_BODY_STRING =
-            "<li><a href=\"#id0\">/file0</a></li>\n"
+    private static final String HTML_OLD_BODY_STRING =
+            "<ul class=\"files\">\n"
+            + "<li><a href=\"#id0\">/file0</a></li>\n"
             + "<li><a href=\"#id1\">/file0</a></li>\n"
             + "<li><a href=\"#id0\">/file1</a></li>\n"
             + "</ul>\n"
@@ -97,66 +105,181 @@
             + "</td></tr><!-- same-license -->\n"
             + "</table></body></html>\n";
 
-    private static final String EXPECTED_HTML_STRING = HTML_HEAD_STRING + HTML_BODY_STRING;
+    private static final String HTML_NEW_BODY_STRING =
+            "<strong>Libraries</strong>\n"
+            + "<ul class=\"libraries\">\n"
+            + "<li><a href=\"#id0\">libA</a></li>\n"
+            + "<li><a href=\"#id1\">libB</a></li>\n"
+            + "</ul>\n"
+            + "<strong>Files</strong>\n"
+            + "<ul class=\"files\">\n"
+            + "<li><a href=\"#id0\">/file0 - libA</a></li>\n"
+            + "<li><a href=\"#id1\">/file0 - libB</a></li>\n"
+            + "<li><a href=\"#id0\">/file1 - libA</a></li>\n"
+            + "</ul>\n"
+            + "</div><!-- table of contents -->\n"
+            + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n"
+            + "<tr id=\"id0\"><td class=\"same-license\">\n"
+            + "<div class=\"label\">Notices for file(s):</div>\n"
+            + "<div class=\"file-list\">\n"
+            + "/file0 <br/>\n"
+            + "/file1 <br/>\n"
+            + "</div><!-- file-list -->\n"
+            + "<pre class=\"license-text\">\n"
+            + "license content #0\n"
+            + "</pre><!-- license-text -->\n"
+            + "</td></tr><!-- same-license -->\n"
+            + "<tr id=\"id1\"><td class=\"same-license\">\n"
+            + "<div class=\"label\">Notices for file(s):</div>\n"
+            + "<div class=\"file-list\">\n"
+            + "/file0 <br/>\n"
+            + "</div><!-- file-list -->\n"
+            + "<pre class=\"license-text\">\n"
+            + "license content #1\n"
+            + "</pre><!-- license-text -->\n"
+            + "</td></tr><!-- same-license -->\n"
+            + "</table></body></html>\n";
 
-    private static final String EXPECTED_HTML_STRING_WITH_CUSTOM_HEADING =
-            HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_BODY_STRING;
+    private static final String EXPECTED_OLD_HTML_STRING = HTML_HEAD_STRING + HTML_OLD_BODY_STRING;
+
+    private static final String EXPECTED_NEW_HTML_STRING = HTML_HEAD_STRING + HTML_NEW_BODY_STRING;
+
+    private static final String EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING =
+            HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_OLD_BODY_STRING;
+
+    private static final String EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING =
+            HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_NEW_BODY_STRING;
 
     @Test
     public void testParseValidXmlStream() throws XmlPullParserException, IOException {
-        Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>();
+        Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
         Map<String, String> contentIdToFileContentMap = new HashMap<>();
 
         LicenseHtmlGeneratorFromXml.parse(
-                new InputStreamReader(new ByteArrayInputStream(VALILD_XML_STRING.getBytes())),
-                fileNameToContentIdMap, contentIdToFileContentMap);
-        assertThat(fileNameToContentIdMap.size()).isEqualTo(2);
-        assertThat(fileNameToContentIdMap.get("/file0")).containsExactly("0");
-        assertThat(fileNameToContentIdMap.get("/file1")).containsExactly("0");
+                new InputStreamReader(new ByteArrayInputStream(VALID_OLD_XML_STRING.getBytes())),
+                fileNameToLibraryToContentIdMap, contentIdToFileContentMap);
+        assertThat(fileNameToLibraryToContentIdMap.size()).isEqualTo(1);
+        assertThat(fileNameToLibraryToContentIdMap.get("").size()).isEqualTo(2);
+        assertThat(fileNameToLibraryToContentIdMap.get("").get("/file0")).containsExactly("0");
+        assertThat(fileNameToLibraryToContentIdMap.get("").get("/file1")).containsExactly("0");
+        assertThat(contentIdToFileContentMap.size()).isEqualTo(1);
+        assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0");
+    }
+
+    @Test
+    public void testParseNewValidXmlStream() throws XmlPullParserException, IOException {
+        Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
+        Map<String, String> contentIdToFileContentMap = new HashMap<>();
+
+        LicenseHtmlGeneratorFromXml.parse(
+                new InputStreamReader(new ByteArrayInputStream(VALID_NEW_XML_STRING.getBytes())),
+                fileNameToLibraryToContentIdMap, contentIdToFileContentMap);
+        assertThat(fileNameToLibraryToContentIdMap.size()).isEqualTo(2);
+        assertThat(fileNameToLibraryToContentIdMap.get("libA").size()).isEqualTo(1);
+        assertThat(fileNameToLibraryToContentIdMap.get("libB").size()).isEqualTo(1);
+        assertThat(fileNameToLibraryToContentIdMap.get("libA").get("/file0")).containsExactly("0");
+        assertThat(fileNameToLibraryToContentIdMap.get("libB").get("/file1")).containsExactly("0");
         assertThat(contentIdToFileContentMap.size()).isEqualTo(1);
         assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0");
     }
 
     @Test(expected = XmlPullParserException.class)
     public void testParseInvalidXmlStream() throws XmlPullParserException, IOException {
-        Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>();
+        Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
         Map<String, String> contentIdToFileContentMap = new HashMap<>();
 
         LicenseHtmlGeneratorFromXml.parse(
-                new InputStreamReader(new ByteArrayInputStream(INVALILD_XML_STRING.getBytes())),
-                fileNameToContentIdMap, contentIdToFileContentMap);
+                new InputStreamReader(new ByteArrayInputStream(INVALID_XML_STRING.getBytes())),
+                fileNameToLibraryToContentIdMap, contentIdToFileContentMap);
     }
 
     @Test
     public void testGenerateHtml() {
-        Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>();
+        Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
         Map<String, String> contentIdToFileContentMap = new HashMap<>();
+        Map<String, Set<String>> toBoth = new HashMap<>();
+        Map<String, Set<String>> toOne = new HashMap<>();
 
-        fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1")));
-        fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0")));
+        toBoth.put("", new HashSet<String>(Arrays.asList("0", "1")));
+        toOne.put("", new HashSet<String>(Arrays.asList("0")));
+
+        fileNameToLibraryToContentIdMap.put("/file0", toBoth);
+        fileNameToLibraryToContentIdMap.put("/file1", toOne);
         contentIdToFileContentMap.put("0", "license content #0");
         contentIdToFileContentMap.put("1", "license content #1");
 
         StringWriter output = new StringWriter();
         LicenseHtmlGeneratorFromXml.generateHtml(
-                fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output), "");
-        assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING);
+                fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+                new PrintWriter(output), "");
+        assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING);
+    }
+
+    @Test
+    public void testGenerateNewHtml() {
+        Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
+        Map<String, String> contentIdToFileContentMap = new HashMap<>();
+        Map<String, Set<String>> toBoth = new HashMap<>();
+        Map<String, Set<String>> toOne = new HashMap<>();
+
+        toBoth.put("libA", new HashSet<String>(Arrays.asList("0")));
+        toBoth.put("libB", new HashSet<String>(Arrays.asList("1")));
+        toOne.put("libA", new HashSet<String>(Arrays.asList("0")));
+
+        fileNameToLibraryToContentIdMap.put("/file0", toBoth);
+        fileNameToLibraryToContentIdMap.put("/file1", toOne);
+        contentIdToFileContentMap.put("0", "license content #0");
+        contentIdToFileContentMap.put("1", "license content #1");
+
+        StringWriter output = new StringWriter();
+        LicenseHtmlGeneratorFromXml.generateHtml(
+                fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+                new PrintWriter(output), "");
+        assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING);
     }
 
     @Test
     public void testGenerateHtmlWithCustomHeading() {
-        Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>();
+        Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
         Map<String, String> contentIdToFileContentMap = new HashMap<>();
+        Map<String, Set<String>> toBoth = new HashMap<>();
+        Map<String, Set<String>> toOne = new HashMap<>();
 
-        fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1")));
-        fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0")));
+        toBoth.put("", new HashSet<String>(Arrays.asList("0", "1")));
+        toOne.put("", new HashSet<String>(Arrays.asList("0")));
+
+        fileNameToLibraryToContentIdMap.put("/file0", toBoth);
+        fileNameToLibraryToContentIdMap.put("/file1", toOne);
         contentIdToFileContentMap.put("0", "license content #0");
         contentIdToFileContentMap.put("1", "license content #1");
 
         StringWriter output = new StringWriter();
         LicenseHtmlGeneratorFromXml.generateHtml(
-                fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output),
-                HTML_CUSTOM_HEADING);
-        assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING_WITH_CUSTOM_HEADING);
+                fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+                new PrintWriter(output), HTML_CUSTOM_HEADING);
+        assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING);
+    }
+
+    @Test
+    public void testGenerateNewHtmlWithCustomHeading() {
+        Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
+        Map<String, String> contentIdToFileContentMap = new HashMap<>();
+        Map<String, Set<String>> toBoth = new HashMap<>();
+        Map<String, Set<String>> toOne = new HashMap<>();
+
+        toBoth.put("libA", new HashSet<String>(Arrays.asList("0")));
+        toBoth.put("libB", new HashSet<String>(Arrays.asList("1")));
+        toOne.put("libA", new HashSet<String>(Arrays.asList("0")));
+
+        fileNameToLibraryToContentIdMap.put("/file0", toBoth);
+        fileNameToLibraryToContentIdMap.put("/file1", toOne);
+        contentIdToFileContentMap.put("0", "license content #0");
+        contentIdToFileContentMap.put("1", "license content #1");
+
+        StringWriter output = new StringWriter();
+        LicenseHtmlGeneratorFromXml.generateHtml(
+                fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+                new PrintWriter(output), HTML_CUSTOM_HEADING);
+        assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
new file mode 100644
index 0000000..72dfc17
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.AudioDeviceInfo;
+import android.media.MediaRoute2Info;
+
+import com.android.settingslib.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class DeviceIconUtilTest {
+    private final DeviceIconUtil mDeviceIconUtil = new DeviceIconUtil();
+
+    @Test
+    public void getIconResIdFromMediaRouteType_usbDevice_isHeadphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_DEVICE))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromMediaRouteType_usbHeadset_isHeadphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_HEADSET))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromMediaRouteType_usbAccessory_isHeadphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_ACCESSORY))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromMediaRouteType_dock_isHeadphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_DOCK))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromMediaRouteType_hdmi_isHeadphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromMediaRouteType_wiredHeadset_isHeadphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADSET))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromMediaRouteType_wiredHeadphones_isHeadphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADPHONES))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromMediaRouteType_builtinSpeaker_isSmartphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER))
+            .isEqualTo(R.drawable.ic_smartphone);
+    }
+
+    @Test
+    public void getIconResIdFromMediaRouteType_unsupportedType_isSmartphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
+            .isEqualTo(R.drawable.ic_smartphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_usbDevice_isHeadphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_DEVICE))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_usbHeadset_isHeadphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_HEADSET))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_usbAccessory_isHeadphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_ACCESSORY))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_dock_isHeadphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_DOCK))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_hdmi_isHeadphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_wiredHeadset_isHeadphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADSET))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_wiredHeadphones_isHeadphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADPHONES))
+            .isEqualTo(R.drawable.ic_headphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_builtinSpeaker_isSmartphone() {
+        assertThat(
+            mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER))
+            .isEqualTo(R.drawable.ic_smartphone);
+    }
+
+    @Test
+    public void getIconResIdFromAudioDeviceType_unsupportedType_isSmartphone() {
+        assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_UNKNOWN))
+            .isEqualTo(R.drawable.ic_smartphone);
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 86ee3b3..0c6d40aa 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -652,29 +652,31 @@
             return Collections.emptySet();
         }
 
-        Cursor cursor = getContentResolver().query(settingsUri, new String[] {
-                Settings.NameValueTable.NAME, Settings.NameValueTable.IS_PRESERVED_IN_RESTORE },
-                /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
+        try (Cursor cursor = getContentResolver().query(settingsUri, new String[]{
+                        Settings.NameValueTable.NAME,
+                        Settings.NameValueTable.IS_PRESERVED_IN_RESTORE},
+                /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null)) {
 
-        if (!cursor.moveToFirst()) {
-            Slog.i(TAG, "No settings to be preserved in restore");
-            return Collections.emptySet();
-        }
-
-        int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
-        int isPreservedIndex = cursor.getColumnIndex(
-                Settings.NameValueTable.IS_PRESERVED_IN_RESTORE);
-
-        Set<String> preservedSettings = new HashSet<>();
-        while (!cursor.isAfterLast()) {
-            if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) {
-                preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex),
-                        settingsUri));
+            if (!cursor.moveToFirst()) {
+                Slog.i(TAG, "No settings to be preserved in restore");
+                return Collections.emptySet();
             }
-            cursor.moveToNext();
-        }
 
-        return preservedSettings;
+            int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+            int isPreservedIndex = cursor.getColumnIndex(
+                    Settings.NameValueTable.IS_PRESERVED_IN_RESTORE);
+
+            Set<String> preservedSettings = new HashSet<>();
+            while (!cursor.isAfterLast()) {
+                if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) {
+                    preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex),
+                            settingsUri));
+                }
+                cursor.moveToNext();
+            }
+
+            return preservedSettings;
+        }
     }
 
     /**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 7ef0901..2e9a16f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -24,6 +24,7 @@
 import android.os.RemoteException
 import android.util.ArrayMap
 import android.util.Log
+import android.util.RotationUtils
 import android.view.IRemoteAnimationFinishedCallback
 import android.view.IRemoteAnimationRunner
 import android.view.RemoteAnimationAdapter
@@ -345,39 +346,33 @@
          * Sets up this rotator.
          *
          * @param rotateDelta is the forward rotation change (the rotation the display is making).
-         * @param displayW (and H) Is the size of the rotating display.
+         * @param parentW (and H) Is the size of the rotating parent.
          */
         fun setup(
             t: SurfaceControl.Transaction,
             parent: SurfaceControl,
             rotateDelta: Int,
-            displayW: Float,
-            displayH: Float
+            parentW: Float,
+            parentH: Float
         ) {
-            var rotateDelta = rotateDelta
             if (rotateDelta == 0) return
-            // We want to counter-rotate, so subtract from 4
-            rotateDelta = 4 - (rotateDelta + 4) % 4
-            surface = SurfaceControl.Builder()
+            val surface = SurfaceControl.Builder()
                     .setName("Transition Unrotate")
                     .setContainerLayer()
                     .setParent(parent)
                     .build()
-            // column-major
-            when (rotateDelta) {
-                1 -> {
-                    t.setMatrix(surface, 0f, 1f, -1f, 0f)
-                    t.setPosition(surface!!, displayW, 0f)
-                }
-                2 -> {
-                    t.setMatrix(surface, -1f, 0f, 0f, -1f)
-                    t.setPosition(surface!!, displayW, displayH)
-                }
-                3 -> {
-                    t.setMatrix(surface, 0f, -1f, 1f, 0f)
-                    t.setPosition(surface!!, 0f, displayH)
-                }
-            }
+            // Rotate forward to match the new rotation (rotateDelta is the forward rotation the
+            // parent already took). Child surfaces will be in the old rotation relative to the new
+            // parent rotation, so we need to forward-rotate the child surfaces to match.
+            RotationUtils.rotateSurface(t, surface, rotateDelta)
+            val tmpPt = Point(0, 0)
+            // parentW/H are the size in the END rotation, the rotation utilities expect the
+            // starting size. So swap them if necessary
+            val flipped = rotateDelta % 2 != 0
+            val pw = if (flipped) parentH else parentW
+            val ph = if (flipped) parentW else parentH
+            RotationUtils.rotatePoint(tmpPt, rotateDelta, pw.toInt(), ph.toInt())
+            t.setPosition(surface, tmpPt.x.toFloat(), tmpPt.y.toFloat())
             t.show(surface)
         }
 
diff --git a/packages/SystemUI/docs/keyguard/aod.md b/packages/SystemUI/docs/keyguard/aod.md
index 6d76ed55..7f89984 100644
--- a/packages/SystemUI/docs/keyguard/aod.md
+++ b/packages/SystemUI/docs/keyguard/aod.md
@@ -1 +1,77 @@
 # Always-on Display (AOD)
+
+AOD provides an alternatative 'screen-off' experience. Instead, of completely turning the display off, it provides a distraction-free, glanceable experience for the phone in a low-powered mode. In this low-powered mode, the display will have a lower refresh rate and the UI should frequently shift its displayed contents in order to prevent burn-in.
+
+The default doze component is specified by `config_dozeComponent` in the [framework config][1]. SystemUI provides a default Doze Component: [DozeService][2]. [DozeService][2] builds a [DozeMachine][3] with dependencies specified in [DozeModule][4] and configurations in [AmbientDisplayConfiguration][13] and [DozeParameters][14].
+
+[DozeMachine][3] handles the following main states:
+* AOD - persistently showing UI when the device is in a low-powered state
+* Pulsing - waking up the screen to show notifications (from AOD and screen off)
+* Docked UI - UI to show when the device is docked
+* Wake-up gestures - including lift to wake and tap to wake (from AOD and screen off)
+
+## Doze States ([see DozeMachine.State][3])
+### DOZE
+Device is asleep and listening for enabled pulsing and wake-up gesture triggers. In this state, no UI shows.
+
+### DOZE_AOD
+Device is asleep, showing UI, and listening for enabled pulsing and wake-up triggers. In this state, screen brightness is handled by [DozeScreenBrightness][5] which uses the brightness sensor specified by `doze_brightness_sensor_type` in the [SystemUI config][6]. To save power, this should be a low-powered sensor that shouldn't trigger as often as the light sensor used for on-screen adaptive brightness.
+
+### DOZE_AOD_PAUSED
+Device is asleep and would normally be in state `DOZE_AOD`; however, instead the display is temporarily off since the proximity sensor reported near for a minimum abount of time. [DozePauser][7] handles transitioning from `DOZE_AOD_PAUSING` after the minimum timeout after the NEAR is reported by the proximity sensor from [DozeTriggers][8]).
+
+### DOZE_PULSING
+Device is awake and showing UI. This is state typically occurs in response to incoming notification, but may also be from other pulse triggers specified in [DozeTriggers][8].
+
+### DOZE_AOD_DOCKED
+Device is awake, showing docking UI and listening for enabled pulsing and wake-up triggers. The default DockManager is provided by an empty interface at [DockManagerImpl][9]. SystemUI should override the DockManager for the DozeService to handle docking events.
+
+[DozeDockHandler][11] listens for Dock state changes from [DockManager][10] and updates the doze docking state.
+
+## Wake-up gestures
+Doze sensors are registered in [DozeTriggers][8] via [DozeSensors][12]. Sensors can be configured per posture for foldable devices.
+
+Relevant sensors include:
+* Proximity sensor
+* Brightness sensor
+* Wake-up gestures
+  * tap to wake
+  * double tap to wake
+  * lift to wake
+  * significant motion
+
+And are configured in the [AmbientDisplayConfiguration][13] with some related configurations specified in [DozeParameters][14].
+
+## Debugging Tips
+Enable DozeLog to print directly to logcat:
+```
+adb shell settings put global systemui/buffer/DozeLog v
+```
+
+Enable all DozeService logs to print directly to logcat:
+```
+adb shell setprop log.tag.DozeService DEBUG
+```
+
+Other helpful dumpsys commands (`adb shell dumpsys <service>`):
+* activity service com.android.systemui/.doze.DozeService
+* activity service com.android.systemui/.SystemUIService
+* display
+* power
+* dreams
+* sensorservice
+
+[1]: /frameworks/base/core/res/res/values/config.xml
+[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+[6]: /frameworks/base/packages/SystemUI/res/values/config.xml
+[7]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
+[8]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+[9]: /frameworks/base/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
+[10]: /frameworks/base/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
+[11]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+[12]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+[13]: /frameworks/base/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+[14]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index c7bc858..757ed76 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -25,6 +25,8 @@
 import com.android.systemui.plugins.annotations.DependsOn;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
+import java.util.ArrayList;
+
 /**
  * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark
  * intensity. Accessible through {@link PluginDependency}
@@ -32,15 +34,15 @@
 @ProvidesInterface(version = DarkIconDispatcher.VERSION)
 @DependsOn(target = DarkReceiver.class)
 public interface DarkIconDispatcher {
-    int VERSION = 1;
+    int VERSION = 2;
 
     /**
      * Sets the dark area so {@link #applyDark} only affects the icons in the specified area.
      *
-     * @param r the area in which icons should change its tint, in logical screen
+     * @param r the areas in which icons should change its tint, in logical screen
      *                 coordinates
      */
-    void setIconsDarkArea(Rect r);
+    void setIconsDarkArea(ArrayList<Rect> r);
 
     /**
      * Adds a receiver to receive callbacks onDarkChanged
@@ -76,8 +78,8 @@
      * @return the tint to apply to view depending on the desired tint color and
      *         the screen tintArea in which to apply that tint
      */
-    static int getTint(Rect tintArea, View view, int color) {
-        if (isInArea(tintArea, view)) {
+    static int getTint(ArrayList<Rect> tintAreas, View view, int color) {
+        if (isInAreas(tintAreas, view)) {
             return color;
         } else {
             return DEFAULT_ICON_TINT;
@@ -85,15 +87,16 @@
     }
 
     /**
-     * @return the dark intensity to apply to view depending on the desired dark
-     *         intensity and the screen tintArea in which to apply that intensity
+     * @return true if more than half of the view area are in any of the given
+     *         areas, false otherwise
      */
-    static float getDarkIntensity(Rect tintArea, View view, float intensity) {
-        if (isInArea(tintArea, view)) {
-            return intensity;
-        } else {
-            return 0f;
+    static boolean isInAreas(ArrayList<Rect> areas, View view) {
+        for (Rect area : areas) {
+            if (isInArea(area, view)) {
+                return true;
+            }
         }
+        return false;
     }
 
     /**
@@ -122,7 +125,7 @@
      */
     @ProvidesInterface(version = DarkReceiver.VERSION)
     interface DarkReceiver {
-        int VERSION = 1;
-        void onDarkChanged(Rect area, float darkIntensity, int tint);
+        int VERSION = 2;
+        void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint);
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
deleted file mode 100644
index 6d1408d..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.plugins.qs;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-@ProvidesInterface(version = DetailAdapter.VERSION)
-public interface DetailAdapter {
-    public static final int VERSION = 1;
-
-    CharSequence getTitle();
-    Boolean getToggleState();
-
-    default boolean getToggleEnabled() {
-        return true;
-    }
-
-    View createDetailView(Context context, View convertView, ViewGroup parent);
-
-    /**
-     * @return intent for opening more settings related to this detail panel. If null, the more
-     * settings button will not be shown
-     */
-    Intent getSettingsIntent();
-
-    /**
-     * @return resource id of the string to use for opening the settings intent. If
-     * {@code Resources.ID_NULL}, then use the default string:
-     * {@code com.android.systemui.R.string.quick_settings_more_settings}
-     */
-    default int getSettingsText() {
-        return Resources.ID_NULL;
-    }
-
-    /**
-     * @return resource id of the string to use for closing the detail panel. If
-     * {@code Resources.ID_NULL}, then use the default string:
-     * {@code com.android.systemui.R.string.quick_settings_done}
-     */
-    default int getDoneText() {
-        return Resources.ID_NULL;
-    }
-
-    void setToggleState(boolean state);
-    int getMetricsCategory();
-
-    /**
-     * Indicates whether the detail view wants to have its header (back button, title and
-     * toggle) shown.
-     */
-    default boolean hasHeader() {
-        return true;
-    }
-
-    /**
-     * Indicates whether the detail view wants to animate when shown. This has no affect over the
-     * closing animation. Detail panels will always animate when closed.
-     */
-    default boolean shouldAnimate() {
-        return true;
-    }
-
-    /**
-     * @return true if the callback handled the event and wants to keep the detail panel open, false
-     * otherwise. Returning false will close the panel.
-     */
-    default boolean onDoneButtonClicked() {
-        return false;
-    }
-
-    default UiEventLogger.UiEventEnum openDetailEvent() {
-        return INVALID;
-    }
-
-    default UiEventLogger.UiEventEnum closeDetailEvent() {
-        return INVALID;
-    }
-
-    default UiEventLogger.UiEventEnum moreSettingsEvent() {
-        return INVALID;
-    }
-
-    UiEventLogger.UiEventEnum INVALID = () -> 0;
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 1ef5324..669d6a3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -35,14 +35,12 @@
 
 @ProvidesInterface(version = QSTile.VERSION)
 @DependsOn(target = QSIconView.class)
-@DependsOn(target = DetailAdapter.class)
 @DependsOn(target = Callback.class)
 @DependsOn(target = Icon.class)
 @DependsOn(target = State.class)
 public interface QSTile {
-    int VERSION = 2;
+    int VERSION = 3;
 
-    DetailAdapter getDetailAdapter();
     String getTileSpec();
 
     boolean isAvailable();
@@ -117,12 +115,9 @@
     }
 
     @ProvidesInterface(version = Callback.VERSION)
-    public interface Callback {
-        public static final int VERSION = 1;
+    interface Callback {
+        static final int VERSION = 2;
         void onStateChanged(State state);
-        void onShowDetail(boolean show);
-        void onToggleStateChanged(boolean state);
-        void onScanStateChanged(boolean state);
     }
 
     @ProvidesInterface(version = Icon.VERSION)
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index ce63b44..6352f81 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,3 +1,6 @@
+# Preserve line number information for debugging stack traces.
+-keepattributes SourceFile,LineNumberTable
+
 -keep class com.android.systemui.recents.OverviewProxyRecentsImpl
 -keep class com.android.systemui.statusbar.car.CarStatusBar
 -keep class com.android.systemui.statusbar.phone.StatusBar
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
index 5343411e..59f87da 100644
--- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
+++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
@@ -16,9 +16,8 @@
 -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dp"
+    android:layout_width="match_parent"
     android:layout_height="@dimen/qs_security_footer_single_line_height"
-    android:layout_weight="1"
     android:gravity="center"
     android:clickable="true"
     android:visibility="gone">
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index e824443..a25ab51 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -28,5 +28,6 @@
     <!-- Will display the bouncer on one side of the display, and the current user icon and
          user switcher on the other side -->
     <bool name="config_enableBouncerUserSwitcher">false</bool>
-
+    <!-- Time to be considered a consecutive fingerprint failure in ms -->
+    <integer name="fp_consecutive_failure_time_ms">3500</integer>
 </resources>
diff --git a/packages/SystemUI/res/color-night/qs_detail_progress_track.xml b/packages/SystemUI/res/color-night/qs_detail_progress_track.xml
deleted file mode 100644
index c56382e..0000000
--- a/packages/SystemUI/res/color-night/qs_detail_progress_track.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- I really don't want to define this, but the View that uses this asset uses both the
-         light and dark accent colors. -->
-    <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_light" />
-</selector>
diff --git a/packages/SystemUI/res/color/qs_detail_progress_track.xml b/packages/SystemUI/res/color/qs_detail_progress_track.xml
deleted file mode 100644
index d86119f..0000000
--- a/packages/SystemUI/res/color/qs_detail_progress_track.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- I really don't want to define this, but the View that uses this asset uses both the
-         light and dark accent colors. -->
-    <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_dark" />
-</selector>
diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml
new file mode 100644
index 0000000..5ca99f3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_account_circle.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M5.85,17.1Q7.125,16.125 8.7,15.562Q10.275,15 12,15Q13.725,15 15.3,15.562Q16.875,16.125 18.15,17.1Q19.025,16.075 19.513,14.775Q20,13.475 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,13.475 4.488,14.775Q4.975,16.075 5.85,17.1ZM12,13Q10.525,13 9.512,11.988Q8.5,10.975 8.5,9.5Q8.5,8.025 9.512,7.012Q10.525,6 12,6Q13.475,6 14.488,7.012Q15.5,8.025 15.5,9.5Q15.5,10.975 14.488,11.988Q13.475,13 12,13ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,20Q13.325,20 14.5,19.613Q15.675,19.225 16.65,18.5Q15.675,17.775 14.5,17.387Q13.325,17 12,17Q10.675,17 9.5,17.387Q8.325,17.775 7.35,18.5Q8.325,19.225 9.5,19.613Q10.675,20 12,20ZM12,11Q12.65,11 13.075,10.575Q13.5,10.15 13.5,9.5Q13.5,8.85 13.075,8.425Q12.65,8 12,8Q11.35,8 10.925,8.425Q10.5,8.85 10.5,9.5Q10.5,10.15 10.925,10.575Q11.35,11 12,11ZM12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5ZM12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_account_circle_filled.xml b/packages/SystemUI/res/drawable/ic_account_circle_filled.xml
new file mode 100644
index 0000000..47c553b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_account_circle_filled.xml
@@ -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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FFFFFF"
+      android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33A7.95,7.95 0,0 1,4 12c0,-4.41 3.59,-8 8,-8s8,3.59 8,8c0,1.82 -0.62,3.49 -1.64,4.83z"/>
+  <path
+      android:fillColor="#FFFFFF"
+      android:pathData="M12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13s3.5,-1.56 3.5,-3.5S13.94,6 12,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/qs_detail_switch.xml b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml
similarity index 62%
rename from packages/SystemUI/res/layout/qs_detail_switch.xml
rename to packages/SystemUI/res/drawable/ic_add_supervised_user.xml
index abb2497..627743e 100644
--- a/packages/SystemUI/res/layout/qs_detail_switch.xml
+++ b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright (C) 2019 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.
@@ -13,11 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<Switch
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/toggle"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:clickable="false"
-    android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
\ No newline at end of file
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:drawable="@*android:drawable/ic_add_supervised_user" />
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_manage_users.xml b/packages/SystemUI/res/drawable/ic_manage_users.xml
new file mode 100644
index 0000000..3a0805d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_manage_users.xml
@@ -0,0 +1,23 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+  <path android:fillColor="@android:color/white"
+        android:pathData="M10,12Q8.35,12 7.175,10.825Q6,9.65 6,8Q6,6.35 7.175,5.175Q8.35,4 10,4Q11.65,4 12.825,5.175Q14,6.35 14,8Q14,9.65 12.825,10.825Q11.65,12 10,12ZM2,20V17.2Q2,16.375 2.425,15.65Q2.85,14.925 3.6,14.55Q4.875,13.9 6.475,13.45Q8.075,13 10,13Q10.2,13 10.35,13Q10.5,13 10.65,13.05Q10.45,13.5 10.312,13.988Q10.175,14.475 10.1,15H10Q8.225,15 6.812,15.45Q5.4,15.9 4.5,16.35Q4.275,16.475 4.138,16.7Q4,16.925 4,17.2V18H10.3Q10.45,18.525 10.7,19.038Q10.95,19.55 11.25,20ZM16,21L15.7,19.5Q15.4,19.375 15.137,19.238Q14.875,19.1 14.6,18.9L13.15,19.35L12.15,17.65L13.3,16.65Q13.25,16.3 13.25,16Q13.25,15.7 13.3,15.35L12.15,14.35L13.15,12.65L14.6,13.1Q14.875,12.9 15.137,12.762Q15.4,12.625 15.7,12.5L16,11H18L18.3,12.5Q18.6,12.625 18.863,12.775Q19.125,12.925 19.4,13.15L20.85,12.65L21.85,14.4L20.7,15.4Q20.75,15.7 20.75,16.025Q20.75,16.35 20.7,16.65L21.85,17.65L20.85,19.35L19.4,18.9Q19.125,19.1 18.863,19.238Q18.6,19.375 18.3,19.5L18,21ZM17,18Q17.825,18 18.413,17.413Q19,16.825 19,16Q19,15.175 18.413,14.587Q17.825,14 17,14Q16.175,14 15.588,14.587Q15,15.175 15,16Q15,16.825 15.588,17.413Q16.175,18 17,18ZM10,10Q10.825,10 11.413,9.412Q12,8.825 12,8Q12,7.175 11.413,6.588Q10.825,6 10,6Q9.175,6 8.588,6.588Q8,7.175 8,8Q8,8.825 8.588,9.412Q9.175,10 10,10ZM10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8ZM10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_detail_background.xml b/packages/SystemUI/res/drawable/qs_detail_background.xml
deleted file mode 100644
index c23649d..0000000
--- a/packages/SystemUI/res/drawable/qs_detail_background.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<transition xmlns:android="http://schemas.android.com/apk/res/android">
-    <item>
-        <inset>
-            <shape>
-                <solid android:color="@android:color/transparent"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius" />
-            </shape>
-        </inset>
-    </item>
-    <item>
-        <inset>
-            <shape>
-                <solid android:color="?android:attr/colorBackgroundFloating"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius" />
-            </shape>
-        </inset>
-    </item>
-</transition>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
deleted file mode 100644
index 78655c0..0000000
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- Extends LinearLayout -->
-<com.android.systemui.qs.QSDetail
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/qs_detail_background"
-    android:layout_marginTop="@dimen/qs_detail_margin_top"
-    android:clickable="true"
-    android:orientation="vertical"
-    android:paddingBottom="8dp"
-    android:visibility="invisible"
-    android:elevation="4dp"
-    android:importantForAccessibility="no" >
-
-    <include
-        android:id="@+id/qs_detail_header"
-        layout="@layout/qs_detail_header"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        />
-
-    <com.android.systemui.statusbar.AlphaOptimizedImageView
-        android:id="@+id/qs_detail_header_progress"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:alpha="0"
-        android:background="@color/qs_detail_progress_track"
-        android:src="@drawable/indeterminate_anim"
-        android:scaleType="fitXY"
-        />
-
-    <ScrollView
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:fillViewport="true">
-
-        <FrameLayout
-            android:id="@android:id/content"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
-    </ScrollView>
-
-    <include layout="@layout/qs_detail_buttons" />
-
-</com.android.systemui.qs.QSDetail>
diff --git a/packages/SystemUI/res/layout/qs_detail_buttons.xml b/packages/SystemUI/res/layout/qs_detail_buttons.xml
deleted file mode 100644
index 75f43f9..0000000
--- a/packages/SystemUI/res/layout/qs_detail_buttons.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingEnd="8dp"
-    android:gravity="end">
-
-    <TextView
-        android:id="@android:id/button2"
-        style="@style/QSBorderlessButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:minHeight="48dp"
-        android:minWidth="132dp"
-        android:textAppearance="@style/TextAppearance.QS.DetailButton"
-        android:focusable="true" />
-
-    <TextView
-        android:id="@android:id/button1"
-        style="@style/QSBorderlessButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minHeight="48dp"
-        android:minWidth="88dp"
-        android:textAppearance="@style/TextAppearance.QS.DetailButton"
-        android:focusable="true"/>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_header.xml b/packages/SystemUI/res/layout/qs_detail_header.xml
deleted file mode 100644
index d1ab054..0000000
--- a/packages/SystemUI/res/layout/qs_detail_header.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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.keyguard.AlphaOptimizedLinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingLeft="@dimen/qs_detail_header_padding"
-    android:paddingTop="@dimen/qs_detail_header_padding"
-    android:paddingBottom="@dimen/qs_detail_items_padding_top"
-    android:paddingEnd="@dimen/qs_panel_padding"
-    android:background="@drawable/btn_borderless_rect"
-    android:orientation="vertical"
-    android:gravity="center">
-
-    <com.android.systemui.ResizingSpace
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/qs_detail_header_margin_top" />
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-
-        <TextView
-            android:id="@android:id/title"
-            android:paddingStart="@dimen/qs_detail_header_text_padding"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:textDirection="locale"
-            android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
-
-        <ImageView
-            android:id="@+id/settings"
-            android:layout_width="@dimen/qs_detail_image_width"
-            android:layout_height="@dimen/qs_detail_image_height"
-            android:background="?android:attr/selectableItemBackground"
-            android:padding="@dimen/qs_detail_image_padding"
-            android:src="@drawable/ic_settings"
-            android:visibility="gone"/>
-
-        <ViewStub
-            android:id="@+id/toggle_stub"
-            android:inflatedId="@+id/toggle"
-            android:layout="@layout/qs_detail_switch"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>
-
-    </LinearLayout>
-
-</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
deleted file mode 100644
index 0844bb4..0000000
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/qs_detail_item_height"
-    android:background="@drawable/btn_borderless_rect"
-    android:clickable="true"
-    android:focusable="true"
-    android:gravity="center_vertical"
-    android:orientation="horizontal" >
-
-    <ImageView
-        android:id="@android:id/icon"
-        android:layout_width="@dimen/qs_detail_item_icon_width"
-        android:layout_height="@dimen/qs_detail_item_icon_size"
-        android:layout_marginStart="@dimen/qs_detail_item_icon_marginStart"
-        android:layout_marginEnd="@dimen/qs_detail_item_icon_marginEnd"
-        android:tint="?android:attr/textColorPrimary"/>
-
-    <LinearLayout
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="12dp"
-        android:layout_weight="1"
-        android:orientation="vertical" >
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textDirection="locale"
-            android:ellipsize="end"
-            android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" />
-
-        <TextView
-            android:id="@android:id/summary"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textDirection="locale"
-            android:layout_marginTop="2dp"
-            android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
-    </LinearLayout>
-
-    <ImageView
-        android:id="@android:id/icon2"
-        style="@style/QSBorderlessButton"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:clickable="true"
-        android:focusable="true"
-        android:scaleType="center"
-        android:contentDescription="@*android:string/media_route_controller_disconnect"
-        android:tint="?android:attr/textColorPrimary" />
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
deleted file mode 100644
index 60cba67..0000000
--- a/packages/SystemUI/res/layout/qs_detail_items.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- extends FrameLayout -->
-<com.android.systemui.qs.QSDetailItems
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:sysui="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:paddingStart="@dimen/qs_detail_padding_start"
-    android:paddingEnd="16dp">
-
-    <com.android.systemui.qs.AutoSizingList
-        android:id="@android:id/list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        sysui:itemHeight="@dimen/qs_detail_item_height"
-        style="@style/AutoSizingList"/>
-
-    <LinearLayout
-        android:id="@android:id/empty"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:gravity="center"
-        android:orientation="vertical">
-
-        <ImageView
-            android:id="@android:id/icon"
-            android:layout_width="56dp"
-            android:layout_height="56dp"
-            android:tint="?android:attr/textColorSecondary" />
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="20dp"
-            android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
-    </LinearLayout>
-</com.android.systemui.qs.QSDetailItems>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 85b33cc..2040051 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -47,10 +47,6 @@
 
     <include layout="@layout/quick_status_bar_expanded_header" />
 
-    <include
-        android:id="@+id/qs_detail"
-        layout="@layout/qs_detail" />
-
     <ViewStub
         android:id="@+id/container_stub"
         android:inflatedId="@+id/qs_footer_actions"
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 3a0df28..0c847ed 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -52,7 +52,7 @@
                 android:gravity="center_horizontal" />
         <ImageView
                 android:id="@+id/restricted_padlock"
-                android:layout_width="@dimen/qs_detail_item_secondary_text_size"
+                android:layout_width="@dimen/qs_tile_text_size"
                 android:layout_height="match_parent"
                 android:gravity="center_vertical"
                 android:src="@drawable/ic_info"
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 4f4bae4..816dfd3 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -19,6 +19,7 @@
     android:id="@+id/system_icons"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
+    android:layout_gravity="center_vertical|end"
     android:gravity="center_vertical">
 
     <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
diff --git a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
deleted file mode 100644
index efe63d7..0000000
--- a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- extends LinearLayout -->
-<com.android.systemui.tuner.TunerZenModePanel
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/tuner_zen_mode_panel"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:clipChildren="false"
-    android:visibility="gone"
-    android:orientation="vertical" >
-
-    <View
-        android:id="@+id/zen_embedded_divider"
-        android:layout_width="match_parent"
-        android:layout_height="1dp"
-        android:layout_marginBottom="12dp"
-        android:layout_marginTop="8dp"
-        android:background="@color/qs_tile_divider" />
-
-    <include
-        android:layout_width="match_parent"
-        android:layout_height="48dp"
-        android:layout_marginStart="16dp"
-        android:id="@+id/tuner_zen_switch"
-        layout="@layout/qs_detail_header" />
-
-    <include layout="@layout/zen_mode_panel" />
-
-    <include
-        android:id="@+id/tuner_zen_buttons"
-        layout="@layout/qs_detail_buttons" />
-
-</com.android.systemui.tuner.TunerZenModePanel>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index 7b95cf3c..1633e52 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -21,6 +21,7 @@
     android:id="@+id/user_switcher_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:layout_marginBottom="64dp"
     android:layout_marginEnd="60dp"
     android:layout_marginStart="60dp">
 
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
index 8d02429..401c4bd 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
@@ -29,17 +29,19 @@
 
     <ImageView
         android:id="@+id/icon"
+        android:scaleType="centerInside"
         android:layout_gravity="center"
         android:layout_width="20dp"
         android:layout_height="20dp"
         android:contentDescription="@null"
+        android:tint="@color/user_switcher_fullscreen_popup_item_tint"
         android:layout_marginEnd="10dp" />
 
     <TextView
         android:id="@+id/text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textColor="@*android:color/text_color_primary_device_default_dark"
+        android:textColor="@color/user_switcher_fullscreen_popup_item_tint"
         android:textSize="14sp"
         android:layout_gravity="start" />
   </LinearLayout>
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
deleted file mode 100644
index 5862413..0000000
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- extends LinearLayout -->
-<com.android.systemui.volume.ZenModePanel xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/zen_mode_panel"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:clipChildren="false" >
-
-    <LinearLayout
-        android:id="@+id/edit_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="?android:attr/colorPrimary"
-        android:clipChildren="false"
-        android:orientation="vertical">
-
-        <com.android.systemui.volume.SegmentedButtons
-            android:id="@+id/zen_buttons"
-            android:background="@drawable/segmented_buttons_background"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="16dp"
-            android:layout_marginBottom="8dp" />
-
-        <RelativeLayout
-            android:id="@+id/zen_introduction"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="16dp"
-            android:paddingTop="8dp"
-            android:paddingBottom="8dp"
-            android:background="@drawable/zen_introduction_message_background"
-            android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent.Light">
-
-            <ImageView
-                android:id="@+id/zen_introduction_confirm"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_marginEnd="8dp"
-                android:layout_alignParentEnd="true"
-                android:background="@drawable/btn_borderless_rect"
-                android:clickable="true"
-                android:contentDescription="@string/accessibility_desc_close"
-                android:scaleType="center"
-                android:src="@drawable/ic_close_white_rounded" />
-
-            <TextView
-                android:id="@+id/zen_introduction_message"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="12dp"
-                android:layout_marginStart="24dp"
-                android:textDirection="locale"
-                android:lineSpacingMultiplier="1.20029"
-                android:layout_toStartOf="@id/zen_introduction_confirm"
-                android:textAppearance="@style/TextAppearance.QS.Introduction" />
-
-            <TextView
-                android:id="@+id/zen_introduction_customize"
-                style="@style/QSBorderlessButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentEnd="true"
-                android:layout_marginEnd="12dp"
-                android:layout_below="@id/zen_introduction_message"
-                android:clickable="true"
-                android:focusable="true"
-                android:text="@string/zen_priority_customize_button"
-                android:textAppearance="@style/TextAppearance.QS.DetailButton.White" />
-
-            <View
-                android:layout_width="0dp"
-                android:layout_height="16dp"
-                android:layout_below="@id/zen_introduction_message"
-                android:layout_alignParentEnd="true" />
-
-        </RelativeLayout>
-
-        <com.android.settingslib.notification.ZenRadioLayout
-            android:id="@+id/zen_conditions"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="8dp"
-            android:layout_marginEnd="4dp"
-            android:layout_marginStart="4dp"
-            android:paddingBottom="@dimen/zen_mode_condition_detail_bottom_padding"
-            android:orientation="horizontal" >
-            <RadioGroup
-                android:id="@+id/zen_radio_buttons"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-            <LinearLayout
-                android:id="@+id/zen_radio_buttons_content"
-                android:layout_width="fill_parent"
-                android:layout_height="fill_parent"
-                android:orientation="vertical"/>
-        </com.android.settingslib.notification.ZenRadioLayout>
-
-        <TextView
-            android:id="@+id/zen_alarm_warning"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="18dp"
-            android:layout_marginEnd="16dp"
-            android:textDirection="locale"
-            android:lineSpacingMultiplier="1.20029"
-            android:textAppearance="@style/TextAppearance.QS.Warning" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@android:id/empty"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:background="?android:attr/colorPrimary"
-        android:gravity="center"
-        android:orientation="vertical">
-
-        <ImageView
-            android:id="@android:id/icon"
-            android:layout_width="56dp"
-            android:layout_height="56dp"
-            android:alpha="?android:attr/disabledAlpha"
-            android:tint="?android:attr/colorForeground" />
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="20dp"
-            android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/auto_rule"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="?android:attr/colorPrimary"
-        android:layout_marginStart="16dp"
-        android:layout_marginEnd="16dp"
-        android:layout_marginTop="16dp"
-        android:layout_marginBottom="8dp"
-        android:orientation="vertical">
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary"/>
-
-    </LinearLayout>
-
-</com.android.systemui.volume.ZenModePanel>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 8c5006d..062e33c 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -33,4 +33,10 @@
 
     <!-- Max number of columns for power menu -->
     <integer name="power_menu_max_columns">4</integer>
+
+    <!-- Max number of columns for power menu lite -->
+    <integer name="power_menu_lite_max_columns">4</integer>
+    <!-- Max number of rows for power menu lite -->
+    <integer name="power_menu_lite_max_rows">2</integer>
+
 </resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 9d24e9b..01eb09b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -38,11 +38,6 @@
     <dimen name="qs_footer_padding">14dp</dimen>
     <dimen name="qs_security_footer_background_inset">12dp</dimen>
 
-    <dimen name="battery_detail_graph_space_top">9dp</dimen>
-    <dimen name="battery_detail_graph_space_bottom">9dp</dimen>
-
-    <dimen name="qs_detail_header_margin_top">14dp</dimen>
-
     <dimen name="volume_tool_tip_top_margin">12dp</dimen>
     <dimen name="volume_row_slider_height">128dp</dimen>
 
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
index d33ee99..7da47e5 100644
--- a/packages/SystemUI/res/values-sw410dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -20,8 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <dimen name="qs_detail_items_padding_top">16dp</dimen>
-
     <!-- Global actions grid -->
     <dimen name="global_actions_grid_vertical_padding">8dp</dimen>
     <dimen name="global_actions_grid_horizontal_padding">4dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index fe546f6..588638f 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -33,4 +33,7 @@
     <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. -->
     <bool name="config_skinnyNotifsInLandscape">false</bool>
 
+    <integer name="power_menu_lite_max_columns">3</integer>
+    <integer name="power_menu_lite_max_rows">2</integer>
+
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index 3c6a81e..857e162 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -23,4 +23,8 @@
 
     <!-- The number of columns in the QuickSettings -->
     <integer name="quick_settings_num_columns">3</integer>
+
+    <integer name="power_menu_lite_max_columns">2</integer>
+    <integer name="power_menu_lite_max_rows">3</integer>
+
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index a66ed15..8f6bde5 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -65,9 +65,6 @@
     <dimen name="qs_security_footer_single_line_height">48dp</dimen>
     <dimen name="qs_security_footer_background_inset">0dp</dimen>
 
-    <!-- When split shade is used, this panel should be aligned to the top -->
-    <dimen name="qs_detail_margin_top">0dp</dimen>
-
     <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
     <dimen name="large_dialog_width">472dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 2eff692..a9e6d22 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -24,29 +24,6 @@
     <string name="config_systemUIFactoryComponent" translatable="false">
         com.android.systemui.tv.TvSystemUIFactory
     </string>
-    <!-- SystemUI Services: The classes of the stuff to start. -->
-    <string-array name="config_systemUIServiceComponents" translatable="false">
-        <item>com.android.systemui.util.NotificationChannels</item>
-        <item>com.android.systemui.volume.VolumeUI</item>
-        <item>com.android.systemui.privacy.television.TvOngoingPrivacyChip</item>
-        <item>com.android.systemui.statusbar.tv.TvStatusBar</item>
-        <item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item>
-        <item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item>
-        <item>com.android.systemui.statusbar.tv.VpnStatusObserver</item>
-        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
-        <item>com.android.systemui.usb.StorageNotification</item>
-        <item>com.android.systemui.power.PowerUI</item>
-        <item>com.android.systemui.media.RingtonePlayer</item>
-        <item>com.android.systemui.keyboard.KeyboardUI</item>
-        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
-        <item>@string/config_systemUIVendorServiceComponent</item>
-        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
-        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
-        <item>com.android.systemui.accessibility.WindowMagnification</item>
-        <item>com.android.systemui.toast.ToastUI</item>
-        <item>com.android.systemui.wmshell.WMShell</item>
-        <item>com.android.systemui.media.systemsounds.HomeSoundEffectController</item>
-    </string-array>
 
     <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
     <integer name="recents_svelte_level">3</integer>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index e6ab0ff9..2992859 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -114,13 +114,6 @@
         <attr name="hybridNotificationTextStyle" format="reference" />
     </declare-styleable>
 
-    <declare-styleable name="AutoSizingList">
-        <!-- Whether AutoSizingList will show only as many items as fit on screen and
-             remove extra items instead of scrolling. -->
-        <attr name="enableAutoSizing" format="boolean" />
-        <attr name="itemHeight" format="dimension" />
-    </declare-styleable>
-
     <declare-styleable name="PluginInflateContainer">
         <attr name="viewType" format="string" />
     </declare-styleable>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1514778..faf518e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -88,6 +88,7 @@
 
     <color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
     <color name="user_switcher_fullscreen_bg">@android:color/system_neutral1_900</color>
+    <color name="user_switcher_fullscreen_popup_item_tint">@*android:color/text_color_primary_device_default_dark</color>
 
     <!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
     <color name="navigation_bar_icon_color">#E5FFFFFF</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 47822b7..bb1ffa8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -292,43 +292,6 @@
     <!-- SystemUIFactory component -->
     <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
 
-    <!-- SystemUI Services: The classes of base stuff to start by default for all
-         configurations. -->
-    <string-array name="config_systemUIServiceComponents" translatable="false">
-        <item>com.android.systemui.util.NotificationChannels</item>
-        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
-        <item>com.android.keyguard.KeyguardBiometricLockoutLogger</item>
-        <item>com.android.systemui.recents.Recents</item>
-        <item>com.android.systemui.volume.VolumeUI</item>
-        <item>com.android.systemui.statusbar.phone.StatusBar</item>
-        <item>com.android.systemui.usb.StorageNotification</item>
-        <item>com.android.systemui.power.PowerUI</item>
-        <item>com.android.systemui.media.RingtonePlayer</item>
-        <item>com.android.systemui.keyboard.KeyboardUI</item>
-        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
-        <item>@string/config_systemUIVendorServiceComponent</item>
-        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
-        <item>com.android.systemui.LatencyTester</item>
-        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
-        <item>com.android.systemui.ScreenDecorations</item>
-        <item>com.android.systemui.biometrics.AuthController</item>
-        <item>com.android.systemui.log.SessionTracker</item>
-        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
-        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
-        <item>com.android.systemui.theme.ThemeOverlayController</item>
-        <item>com.android.systemui.accessibility.WindowMagnification</item>
-        <item>com.android.systemui.accessibility.SystemActions</item>
-        <item>com.android.systemui.toast.ToastUI</item>
-        <item>com.android.systemui.wmshell.WMShell</item>
-        <item>com.android.systemui.clipboardoverlay.ClipboardListener</item>
-    </string-array>
-
-    <!-- SystemUI Services: The classes of the additional stuff to start. Services here are
-                            specified as an overlay to provide configuration-specific services that
-                            supplement those listed in config_systemUIServiceComponents. -->
-    <string-array name="config_additionalSystemUIServiceComponents" translatable="false">
-    </string-array>
-
     <!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
     <dimen name="config_qsTileStrokeWidthActive">-1dp</dimen>
     <dimen name="config_qsTileStrokeWidthInactive">-1dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fe79f27..d1f4f19 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -500,26 +500,10 @@
     <dimen name="qs_panel_elevation">4dp</dimen>
     <dimen name="qs_panel_padding_bottom">@dimen/new_footer_height</dimen>
     <dimen name="qs_panel_padding_top">48dp</dimen>
-    <dimen name="qs_detail_header_padding">0dp</dimen>
-    <dimen name="qs_detail_image_width">56dp</dimen>
-    <dimen name="qs_detail_image_height">56dp</dimen>
-    <dimen name="qs_detail_image_padding">16dp</dimen>
-    <dimen name="qs_detail_item_height">48dp</dimen>
-    <dimen name="qs_detail_header_text_size">20sp</dimen>
-    <dimen name="qs_detail_button_text_size">14sp</dimen>
-    <dimen name="qs_detail_item_primary_text_size">16sp</dimen>
-    <dimen name="qs_detail_item_secondary_text_size">14sp</dimen>
-    <dimen name="qs_detail_empty_text_size">14sp</dimen>
-    <dimen name="qs_detail_header_margin_top">28dp</dimen>
-    <dimen name="qs_detail_header_text_padding">16dp</dimen>
+
     <dimen name="qs_data_usage_text_size">14sp</dimen>
     <dimen name="qs_data_usage_usage_text_size">36sp</dimen>
-    <dimen name="qs_detail_padding_start">16dp</dimen>
-    <dimen name="qs_detail_items_padding_top">4dp</dimen>
-    <dimen name="qs_detail_item_icon_size">24dp</dimen>
-    <dimen name="qs_detail_item_icon_width">32dp</dimen>
-    <dimen name="qs_detail_item_icon_marginStart">0dp</dimen>
-    <dimen name="qs_detail_item_icon_marginEnd">20dp</dimen>
+
     <dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
     <dimen name="qs_header_carrier_separator_width">6dp</dimen>
     <dimen name="qs_carrier_margin_width">4dp</dimen>
@@ -533,9 +517,6 @@
     <dimen name="qs_security_footer_background_inset">0dp</dimen>
     <dimen name="qs_security_footer_corner_radius">28dp</dimen>
 
-    <!-- Desired qs icon overlay size. -->
-    <dimen name="qs_detail_icon_overlay_size">24dp</dimen>
-
     <dimen name="segmented_button_spacing">0dp</dimen>
     <dimen name="borderless_button_radius">2dp</dimen>
 
@@ -547,8 +528,6 @@
     <!-- Padding between subtitles and the following text in the QSFooter dialog -->
     <dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen>
 
-    <dimen name="qs_detail_margin_top">@*android:dimen/quick_qs_offset_height</dimen>
-
     <!-- Zen mode panel: spacing between two-line condition upper and lower lines -->
     <dimen name="zen_mode_condition_detail_item_interline_spacing">4dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d39e295..3b7e9d4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -609,8 +609,6 @@
     <string name="quick_settings_inversion_label">Color inversion</string>
     <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_color_correction_label">Color correction</string>
-    <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_more_settings">More settings</string>
     <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_more_user_settings">User settings</string>
     <!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
@@ -1213,9 +1211,6 @@
     <!-- Alarm template for far alarms [CHAR LIMIT=25] -->
     <string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string>
 
-    <!-- Accessibility label for Quick Settings detail screens [CHAR LIMIT=NONE] -->
-    <string name="accessibility_quick_settings_detail">Quick Settings, <xliff:g id="title" example="Wi-Fi">%s</xliff:g>.</string>
-
     <!-- Accessibility label for hotspot icon [CHAR LIMIT=NONE] -->
     <string name="accessibility_status_bar_hotspot">Hotspot</string>
 
@@ -1731,11 +1726,6 @@
     <string name="data_connection_no_internet">No internet</string>
 
     <!-- accessibility label for quick settings items that open a details page [CHAR LIMIT=NONE] -->
-    <string name="accessibility_quick_settings_open_details">Open details.</string>
-
-    <!-- accessibility label for quick settings items that are currently disabled. Must have a reason [CHAR LIMIT=NONE] -->
-
-    <!-- accessibility label for quick settings items that open a details page [CHAR LIMIT=NONE] -->
     <string name="accessibility_quick_settings_open_settings">Open <xliff:g name="page" example="Bluetooth">%s</xliff:g> settings.</string>
 
     <!-- accessibility label for button to edit quick settings [CHAR LIMIT=NONE] -->
@@ -2402,4 +2392,8 @@
 
     <!-- Generic "add" string [CHAR LIMIT=NONE] -->
     <string name="add">Add</string>
+    <!-- Add supervised user -->
+    <string name="add_user_supervised" translatable="false">@*android:string/supervised_user_creation_label</string>
+    <!-- Manage users - For system user management [CHAR LIMIT=40]  -->
+    <string name="manage_users">Manage users</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9448d3f..5d252fd 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -96,17 +96,12 @@
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
-    <style name="TextAppearance.QS.DetailHeader">
-        <item name="android:textSize">@dimen/qs_detail_header_text_size</item>
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
-    </style>
-
     <style name="TextAppearance.QS.DetailItemPrimary">
-        <item name="android:textSize">@dimen/qs_detail_item_primary_text_size</item>
+        <item name="android:textSize">@dimen/qs_tile_text_size</item>
     </style>
 
     <style name="TextAppearance.QS.DetailItemSecondary">
-        <item name="android:textSize">@dimen/qs_detail_item_secondary_text_size</item>
+        <item name="android:textSize">@dimen/qs_tile_text_size</item>
         <item name="android:textColor">?android:attr/colorAccent</item>
     </style>
 
@@ -120,23 +115,6 @@
         <item name="android:textColor">?android:attr/colorError</item>
     </style>
 
-    <style name="TextAppearance.QS.DetailButton">
-        <item name="android:textSize">@dimen/qs_detail_button_text_size</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
-        <item name="android:textAllCaps">true</item>
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
-        <item name="android:gravity">center</item>
-    </style>
-
-    <style name="TextAppearance.QS.DetailButton.White">
-        <item name="android:textColor">@color/zen_introduction</item>
-    </style>
-
-    <style name="TextAppearance.QS.DetailEmpty">
-        <item name="android:textSize">@dimen/qs_detail_empty_text_size</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
-    </style>
-
     <style name="TextAppearance.QS.SegmentedButton">
         <item name="android:textSize">16sp</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
@@ -167,7 +145,7 @@
     </style>
 
     <style name="TextAppearance.QS.UserSwitcher">
-        <item name="android:textSize">@dimen/qs_detail_item_secondary_text_size</item>
+        <item name="android:textSize">@dimen/qs_tile_text_size</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
@@ -433,9 +411,6 @@
         <item name="numColumns">3</item>
     </style>
 
-    <style name="AutoSizingList">
-        <item name="enableAutoSizing">true</item>
-    </style>
     <style name="Theme.SystemUI.MediaProjectionAlertDialog">
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2ef8d6d..99e0ec1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -86,8 +86,6 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
-import androidx.lifecycle.Observer;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.util.LatencyTracker;
@@ -109,7 +107,6 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.Assert;
-import com.android.systemui.util.RingerModeTracker;
 
 import com.google.android.collect.Lists;
 
@@ -146,7 +143,6 @@
     private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE;
     private static final boolean DEBUG_SPEW = false;
     private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
-    private int mNumActiveUnlockTriggers = 0;
 
     private static final String ACTION_FACE_UNLOCK_STARTED
             = "com.android.facelock.FACE_UNLOCK_STARTED";
@@ -157,7 +153,6 @@
     private static final int MSG_TIME_UPDATE = 301;
     private static final int MSG_BATTERY_UPDATE = 302;
     private static final int MSG_SIM_STATE_CHANGE = 304;
-    private static final int MSG_RINGER_MODE_CHANGED = 305;
     private static final int MSG_PHONE_STATE_CHANGED = 306;
     private static final int MSG_DEVICE_PROVISIONED = 308;
     private static final int MSG_DPM_STATE_CHANGED = 309;
@@ -313,7 +308,6 @@
     private TrustManager mTrustManager;
     private UserManager mUserManager;
     private KeyguardBypassController mKeyguardBypassController;
-    private RingerModeTracker mRingerModeTracker;
     private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
     private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     private boolean mIsFaceAuthUserRequested;
@@ -325,8 +319,6 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
     private boolean mLogoutEnabled;
-    // cached value to avoid IPCs
-    private boolean mIsUdfpsEnrolled;
     private boolean mIsFaceEnrolled;
     // If the user long pressed the lock icon, disabling face auth for the current session.
     private boolean mLockIconPressed;
@@ -362,13 +354,6 @@
 
     private final Handler mHandler;
 
-    private final Observer<Integer> mRingerModeObserver = new Observer<Integer>() {
-        @Override
-        public void onChanged(Integer ringer) {
-            mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, ringer, 0).sendToTarget();
-        }
-    };
-
     private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
     private BiometricManager mBiometricManager;
     private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -1808,10 +1793,6 @@
         mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     }
 
-    private void registerRingerTracker() {
-        mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver);
-    }
-
     @VisibleForTesting
     @Inject
     protected KeyguardUpdateMonitor(
@@ -1819,7 +1800,6 @@
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
             DumpManager dumpManager,
-            RingerModeTracker ringerModeTracker,
             @Background Executor backgroundExecutor,
             @Main Executor mainExecutor,
             StatusBarStateController statusBarStateController,
@@ -1837,7 +1817,6 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mInteractionJankMonitor = interactionJankMonitor;
         mLatencyTracker = latencyTracker;
-        mRingerModeTracker = ringerModeTracker;
         mStatusBarStateController = statusBarStateController;
         mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
         mStatusBarState = mStatusBarStateController.getState();
@@ -1862,9 +1841,6 @@
                     case MSG_SIM_STATE_CHANGE:
                         handleSimStateChange(msg.arg1, msg.arg2, (int) msg.obj);
                         break;
-                    case MSG_RINGER_MODE_CHANGED:
-                        handleRingerModeChange(msg.arg1);
-                        break;
                     case MSG_PHONE_STATE_CHANGED:
                         handlePhoneStateChanged((String) msg.obj);
                         break;
@@ -2006,8 +1982,6 @@
             }
         });
 
-        mHandler.post(this::registerRingerTracker);
-
         final IntentFilter allUserFilter = new IntentFilter();
         allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
         allUserFilter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
@@ -2117,10 +2091,6 @@
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
     }
 
-    private void updateUdfpsEnrolled(int userId) {
-        mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId);
-    }
-
     private void updateFaceEnrolled(int userId) {
         mIsFaceEnrolled = whitelistIpcs(
                 () -> mFaceManager != null && mFaceManager.isHardwareDetected()
@@ -2132,7 +2102,7 @@
      * @return true if there's at least one udfps enrolled for the current user.
      */
     public boolean isUdfpsEnrolled() {
-        return mIsUdfpsEnrolled;
+        return mAuthController.isUdfpsEnrolled(getCurrentUser());
     }
 
     /**
@@ -2186,7 +2156,6 @@
             return;
         }
 
-        updateUdfpsEnrolled(getCurrentUser());
         final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
         final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
                 || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
@@ -2750,12 +2719,6 @@
         Assert.isMainThread();
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         updateSecondaryLockscreenRequirement(userId);
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onDevicePolicyManagerStateChanged();
-            }
-        }
     }
 
     /**
@@ -2834,21 +2797,6 @@
     }
 
     /**
-     * Handle {@link #MSG_RINGER_MODE_CHANGED}
-     */
-    private void handleRingerModeChange(int mode) {
-        Assert.isMainThread();
-        if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")");
-        mRingMode = mode;
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onRingerModeChanged(mode);
-            }
-        }
-    }
-
-    /**
      * Handle {@link #MSG_TIME_UPDATE}
      */
     private void handleTimeUpdate() {
@@ -3235,7 +3183,6 @@
         // Notify listener of the current state
         callback.onRefreshBatteryInfo(mBatteryStatus);
         callback.onTimeChanged();
-        callback.onRingerModeChanged(mRingMode);
         callback.onPhoneStateChanged(mPhoneState);
         callback.onRefreshCarrierInfo();
         callback.onClockVisibilityChanged();
@@ -3546,7 +3493,6 @@
 
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
-        mRingerModeTracker.getRingerMode().removeObserver(mRingerModeObserver);
 
         mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
         mTrustManager.unregisterTrustListener(this);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 47e1035..8d5603d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -15,10 +15,7 @@
  */
 package com.android.keyguard;
 
-import android.app.admin.DevicePolicyManager;
-import android.graphics.Bitmap;
 import android.hardware.biometrics.BiometricSourceType;
-import android.media.AudioManager;
 import android.os.SystemClock;
 import android.telephony.TelephonyManager;
 import android.view.WindowManagerPolicyConstants;
@@ -70,13 +67,6 @@
     public void onRefreshCarrierInfo() { }
 
     /**
-     * Called when the ringer mode changes.
-     * @param state the current ringer state, as defined in
-     * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
-     */
-    public void onRingerModeChanged(int state) { }
-
-    /**
      * Called when the phone state changes. String will be one of:
      * {@link TelephonyManager#EXTRA_STATE_IDLE}
      * {@link TelephonyManager@EXTRA_STATE_RINGING}
@@ -124,12 +114,6 @@
     public void onDeviceProvisioned() { }
 
     /**
-     * Called when the device policy changes.
-     * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED}
-     */
-    public void onDevicePolicyManagerStateChanged() { }
-
-    /**
      * Called when the user change begins.
      */
     public void onUserSwitching(int userId) { }
@@ -168,14 +152,7 @@
     public void onEmergencyCallAction() { }
 
     /**
-     * Called when the transport background changes.
-     * @param bitmap
-     */
-    public void onSetBackground(Bitmap bitmap) {
-    }
-
-    /**
-     * Called when the device has started waking up.
+     * Called when the device has started waking up and after biometric states are updated.
      *
      * @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}.
      */
@@ -183,7 +160,8 @@
     public void onStartedWakingUp() { }
 
     /**
-     * Called when the device has started going to sleep.
+     * Called when the device has started going to sleep and after biometric recognized
+     * states are reset.
      * @param why see {@link #onFinishedGoingToSleep(int)}
      *
      * @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}.
@@ -304,7 +282,6 @@
      */
     public void onTrustAgentErrorMessage(CharSequence message) { }
 
-
     /**
      * Called when a value of logout enabled is change.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 80a3a0e..4ad5183 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -433,7 +433,6 @@
                 public void onDozingChanged(boolean isDozing) {
                     mIsDozing = isDozing;
                     updateBurnInOffsets();
-                    updateIsUdfpsEnrolled();
                     updateVisibility();
                 }
 
@@ -513,7 +512,6 @@
                     mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
                         KeyguardUpdateMonitor.getCurrentUser());
             }
-            updateIsUdfpsEnrolled();
             updateVisibility();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt b/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt
index 42d38cb..13d96e4 100644
--- a/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt
@@ -32,11 +32,11 @@
     private val dualToneHandler = DualToneHandler(context)
 
     init {
-        onDarkChanged(Rect(), 1f, DarkIconDispatcher.DEFAULT_ICON_TINT)
+        onDarkChanged(ArrayList<Rect>(), 1f, DarkIconDispatcher.DEFAULT_ICON_TINT)
     }
 
-    override fun onDarkChanged(area: Rect?, darkIntensity: Float, tint: Int) {
-        val intensity = if (DarkIconDispatcher.isInArea(area, this)) darkIntensity else 0f
+    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+        val intensity = if (DarkIconDispatcher.isInAreas(areas, this)) darkIntensity else 0f
         setBackgroundColor(dualToneHandler.getSingleColor(intensity))
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index c7f1006..95f666c 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -60,6 +60,7 @@
         super(context, attrs);
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer);
         String viewType = a.getString(R.styleable.PluginInflateContainer_viewType);
+        a.recycle();
         try {
             mClass = (Class<ViewProvider>) Class.forName(viewType);
         } catch (Exception e) {
diff --git a/packages/SystemUI/src/com/android/systemui/ResizingSpace.java b/packages/SystemUI/src/com/android/systemui/ResizingSpace.java
index c2bc53e..bb4176d 100644
--- a/packages/SystemUI/src/com/android/systemui/ResizingSpace.java
+++ b/packages/SystemUI/src/com/android/systemui/ResizingSpace.java
@@ -35,6 +35,7 @@
         TypedArray a = context.obtainStyledAttributes(attrs, android.R.styleable.ViewGroup_Layout);
         mWidth = a.getResourceId(android.R.styleable.ViewGroup_Layout_layout_width, 0);
         mHeight = a.getResourceId(android.R.styleable.ViewGroup_Layout_layout_height, 0);
+        a.recycle();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 5bdee2a..23ca923 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -49,8 +49,11 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.inject.Provider;
 
 /**
  * Application class for SystemUI.
@@ -181,17 +184,16 @@
      */
 
     public void startServicesIfNeeded() {
-        final String[] names = SystemUIFactory.getInstance()
-                .getSystemUIServiceComponents(getResources());
-        final String[] additionalNames = SystemUIFactory.getInstance()
-                .getAdditionalSystemUIServiceComponents(getResources());
+        final String vendorComponent = SystemUIFactory.getInstance()
+                .getVendorComponent(getResources());
 
-        final ArrayList<String> serviceComponents = new ArrayList<>();
-        Collections.addAll(serviceComponents, names);
-        Collections.addAll(serviceComponents, additionalNames);
-
-        startServicesIfNeeded(/* metricsPrefix= */ "StartServices",
-                serviceComponents.toArray(new String[serviceComponents.size()]));
+        // Sort the startables so that we get a deterministic ordering.
+        // TODO: make #start idempotent and require users of CoreStartable to call it.
+        Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
+                Comparator.comparing(Class::getName));
+        sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponents());
+        startServicesIfNeeded(
+                sortedStartables, "StartServices", vendorComponent);
     }
 
     /**
@@ -201,16 +203,22 @@
      * <p>This method must only be called from the main thread.</p>
      */
     void startSecondaryUserServicesIfNeeded() {
-        String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponentsPerUser(
-                getResources());
-        startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names);
+        // Sort the startables so that we get a deterministic ordering.
+        Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
+                Comparator.comparing(Class::getName));
+        sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponentsPerUser());
+        startServicesIfNeeded(
+                sortedStartables, "StartSecondaryServices", null);
     }
 
-    private void startServicesIfNeeded(String metricsPrefix, String[] services) {
+    private void startServicesIfNeeded(
+            Map<Class<?>, Provider<CoreStartable>> startables,
+            String metricsPrefix,
+            String vendorComponent) {
         if (mServicesStarted) {
             return;
         }
-        mServices = new CoreStartable[services.length];
+        mServices = new CoreStartable[startables.size() + (vendorComponent == null ? 0 : 1)];
 
         if (!mBootCompleteCache.isBootComplete()) {
             // check to see if maybe it was already completed long before we began
@@ -230,36 +238,29 @@
         TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
                 Trace.TRACE_TAG_APP);
         log.traceBegin(metricsPrefix);
-        final int N = services.length;
-        for (int i = 0; i < N; i++) {
-            String clsName = services[i];
-            if (DEBUG) Log.d(TAG, "loading: " + clsName);
-            log.traceBegin(metricsPrefix + clsName);
-            long ti = System.currentTimeMillis();
-            try {
-                CoreStartable obj = mComponentHelper.resolveCoreStartable(clsName);
-                if (obj == null) {
-                    Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
-                    obj = (CoreStartable) constructor.newInstance(this);
-                }
-                mServices[i] = obj;
-            } catch (ClassNotFoundException
-                    | NoSuchMethodException
-                    | IllegalAccessException
-                    | InstantiationException
-                    | InvocationTargetException ex) {
-                throw new RuntimeException(ex);
-            }
 
-            if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
-            mServices[i].start();
-            log.traceEnd();
+        int i = 0;
+        for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) {
+            String clsName = entry.getKey().getName();
+            int j = i;  // Copied to make lambda happy.
+            timeInitialization(
+                    clsName,
+                    () -> mServices[j] = startStartable(clsName, entry.getValue()),
+                    log,
+                    metricsPrefix);
+            i++;
+        }
 
-            // Warn if initialization of component takes too long
-            ti = System.currentTimeMillis() - ti;
-            if (ti > 1000) {
-                Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
-            }
+        if (vendorComponent != null) {
+            timeInitialization(
+                    vendorComponent,
+                    () -> mServices[mServices.length - 1] =
+                            startAdditionalStartable(vendorComponent),
+                    log,
+                    metricsPrefix);
+        }
+
+        for (i = 0; i < mServices.length; i++) {
             if (mBootCompleteCache.isBootComplete()) {
                 mServices[i].onBootCompleted();
             }
@@ -272,6 +273,50 @@
         mServicesStarted = true;
     }
 
+    private void timeInitialization(String clsName, Runnable init, TimingsTraceLog log,
+            String metricsPrefix) {
+        long ti = System.currentTimeMillis();
+        log.traceBegin(metricsPrefix + " " + clsName);
+        init.run();
+        log.traceEnd();
+
+        // Warn if initialization of component takes too long
+        ti = System.currentTimeMillis() - ti;
+        if (ti > 1000) {
+            Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
+        }
+    }
+
+    private CoreStartable startAdditionalStartable(String clsName) {
+        CoreStartable startable;
+        if (DEBUG) Log.d(TAG, "loading: " + clsName);
+        try {
+            Constructor<?> constructor = Class.forName(clsName).getConstructor(
+                    Context.class);
+            startable = (CoreStartable) constructor.newInstance(this);
+        } catch (ClassNotFoundException
+                | NoSuchMethodException
+                | IllegalAccessException
+                | InstantiationException
+                | InvocationTargetException ex) {
+            throw new RuntimeException(ex);
+        }
+
+        return startStartable(startable);
+    }
+
+    private CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
+        if (DEBUG) Log.d(TAG, "loading: " + clsName);
+        return startStartable(provider.get());
+    }
+
+    private CoreStartable startStartable(CoreStartable startable) {
+        if (DEBUG) Log.d(TAG, "running: " + startable);
+        startable.start();
+
+        return startable;
+    }
+
     // TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
     @Override
     public boolean addDumpable(Dumpable dumpable) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index b3be877..11fffd0 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -32,10 +32,13 @@
 import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
 import com.android.wm.shell.transition.ShellTransitions;
 
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 
+import javax.inject.Provider;
+
 /**
  * Class factory to provide customizable SystemUI components.
  */
@@ -190,24 +193,24 @@
     }
 
     /**
-     * Returns the list of system UI components that should be started.
+     * Returns the list of {@link CoreStartable} components that should be started at startup.
      */
-    public String[] getSystemUIServiceComponents(Resources resources) {
-        return resources.getStringArray(R.array.config_systemUIServiceComponents);
+    public Map<Class<?>, Provider<CoreStartable>> getStartableComponents() {
+        return mSysUIComponent.getStartables();
     }
 
     /**
      * Returns the list of additional system UI components that should be started.
      */
-    public String[] getAdditionalSystemUIServiceComponents(Resources resources) {
-        return resources.getStringArray(R.array.config_additionalSystemUIServiceComponents);
+    public String getVendorComponent(Resources resources) {
+        return resources.getString(R.string.config_systemUIVendorServiceComponent);
     }
 
     /**
-     * Returns the list of system UI components that should be started per user.
+     * Returns the list of {@link CoreStartable} components that should be started per user.
      */
-    public String[] getSystemUIServiceComponentsPerUser(Resources resources) {
-        return resources.getStringArray(R.array.config_systemUIServiceComponentsPerUser);
+    public Map<Class<?>, Provider<CoreStartable>> getStartableComponentsPerUser() {
+        return mSysUIComponent.getPerUserStartables();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index f8e7697..2b0c083 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -56,6 +56,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.text.NumberFormat;
+import java.util.ArrayList;
 
 public class BatteryMeterView extends LinearLayout implements DarkReceiver {
 
@@ -125,7 +126,7 @@
         updateShowPercent();
         mDualToneHandler = new DualToneHandler(context);
         // Init to not dark at all.
-        onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
+        onDarkChanged(new ArrayList<Rect>(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
 
         setClipChildren(false);
         setClipToPadding(false);
@@ -353,8 +354,8 @@
     }
 
     @Override
-    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0;
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        float intensity = DarkIconDispatcher.isInAreas(areas, this) ? darkIntensity : 0;
         mNonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity);
         mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
         mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 72b40d4..54664f2 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -48,7 +48,7 @@
     @Override
     public void start() {
         if (DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) {
+                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) {
             mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class));
             mClipboardManager.addPrimaryClipChangedListener(this);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 236c43b..40689ee 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -262,7 +262,8 @@
             resetActionChips();
             for (RemoteAction action : actions) {
                 Intent targetIntent = action.getActionIntent().getIntent();
-                if (!TextUtils.equals(source, targetIntent.getComponent().getPackageName())) {
+                ComponentName component = targetIntent.getComponent();
+                if (component != null && !TextUtils.equals(source, component.getPackageName())) {
                     OverlayActionChip chip = constructActionChip(action);
                     mActionContainer.addView(chip);
                     mActionChips.add(chip);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
index f53221c..e868d43a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
@@ -20,7 +20,6 @@
 import android.app.Service;
 import android.content.BroadcastReceiver;
 
-import com.android.systemui.CoreStartable;
 import com.android.systemui.recents.RecentsImplementation;
 
 /**
@@ -37,8 +36,5 @@
     Service resolveService(String className);
 
     /** Turns a classname into an instance of the class or returns null. */
-    CoreStartable resolveCoreStartable(String className);
-
-    /** Turns a classname into an instance of the class or returns null. */
     BroadcastReceiver resolveBroadcastReceiver(String className);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
index fba8d35..3607e25 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
@@ -20,7 +20,6 @@
 import android.app.Service;
 import android.content.BroadcastReceiver;
 
-import com.android.systemui.CoreStartable;
 import com.android.systemui.recents.RecentsImplementation;
 
 import java.util.Map;
@@ -35,19 +34,16 @@
 public class ContextComponentResolver implements ContextComponentHelper {
     private final Map<Class<?>, Provider<Activity>> mActivityCreators;
     private final Map<Class<?>, Provider<Service>> mServiceCreators;
-    private final Map<Class<?>, Provider<CoreStartable>> mSystemUICreators;
     private final Map<Class<?>, Provider<RecentsImplementation>> mRecentsCreators;
     private final Map<Class<?>, Provider<BroadcastReceiver>> mBroadcastReceiverCreators;
 
     @Inject
     ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators,
             Map<Class<?>, Provider<Service>> serviceCreators,
-            Map<Class<?>, Provider<CoreStartable>> systemUICreators,
             Map<Class<?>, Provider<RecentsImplementation>> recentsCreators,
             Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) {
         mActivityCreators = activityCreators;
         mServiceCreators = serviceCreators;
-        mSystemUICreators = systemUICreators;
         mRecentsCreators = recentsCreators;
         mBroadcastReceiverCreators = broadcastReceiverCreators;
     }
@@ -84,14 +80,6 @@
         return resolve(className, mServiceCreators);
     }
 
-    /**
-     * Looks up the SystemUI class name to see if Dagger has an instance of it.
-     */
-    @Override
-    public CoreStartable resolveCoreStartable(String className) {
-        return resolve(className, mSystemUICreators);
-    }
-
     private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) {
         try {
             Class<?> clazz = Class.forName(className);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 33f07c7..e1cbdcd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -25,6 +25,7 @@
 import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
 import com.android.systemui.keyguard.KeyguardService;
 import com.android.systemui.screenrecord.RecordingService;
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 
 import dagger.Binds;
 import dagger.Module;
@@ -63,6 +64,13 @@
     /** */
     @Binds
     @IntoMap
+    @ClassKey(NotificationListenerWithPlugins.class)
+    public abstract Service bindNotificationListenerWithPlugins(
+            NotificationListenerWithPlugins service);
+
+    /** */
+    @Binds
+    @IntoMap
     @ClassKey(SystemUIService.class)
     public abstract Service bindSystemUIService(SystemUIService service);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index bda8e3c..81fa99a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -18,11 +18,14 @@
 
 import com.android.keyguard.clock.ClockOptionsProvider;
 import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.dagger.qualifiers.PerUser;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
 import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
@@ -51,8 +54,11 @@
 import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
 import com.android.wm.shell.transition.ShellTransitions;
 
+import java.util.Map;
 import java.util.Optional;
 
+import javax.inject.Provider;
+
 import dagger.BindsInstance;
 import dagger.Subcomponent;
 
@@ -65,6 +71,7 @@
         DependencyProvider.class,
         SystemUIBinder.class,
         SystemUIModule.class,
+        SystemUICoreStartableModule.class,
         SystemUIDefaultModule.class})
 public interface SysUIComponent {
 
@@ -144,6 +151,7 @@
         getMediaTttChipControllerSender();
         getMediaTttChipControllerReceiver();
         getMediaTttCommandLineHelper();
+        getMediaMuteAwaitConnectionCli();
         getUnfoldLatencyTracker().init();
         getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
         getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -220,6 +228,19 @@
     /** */
     Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
 
+    /** */
+    Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
+
+    /**
+     * Returns {@link CoreStartable}s that should be started with the application.
+     */
+    Map<Class<?>, Provider<CoreStartable>> getStartables();
+
+    /**
+     * Returns {@link CoreStartable}s that should be started for every user.
+     */
+    @PerUser Map<Class<?>, Provider<CoreStartable>> getPerUserStartables();
+
     /**
      * Member injection into the supplied argument.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index ec2beb1..b32f878 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -16,46 +16,11 @@
 
 package com.android.systemui.dagger;
 
-import com.android.keyguard.KeyguardBiometricLockoutLogger;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.LatencyTester;
-import com.android.systemui.ScreenDecorations;
-import com.android.systemui.SliceBroadcastRelayHandler;
-import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.accessibility.WindowMagnification;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.clipboardoverlay.ClipboardListener;
-import com.android.systemui.dreams.DreamOverlayRegistrant;
-import com.android.systemui.dreams.SmartSpaceComplication;
-import com.android.systemui.dreams.complication.DreamClockDateComplication;
-import com.android.systemui.dreams.complication.DreamClockTimeComplication;
-import com.android.systemui.dreams.complication.DreamWeatherComplication;
-import com.android.systemui.globalactions.GlobalActionsComponent;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
-import com.android.systemui.log.SessionTracker;
-import com.android.systemui.media.dream.MediaDreamSentinel;
-import com.android.systemui.media.systemsounds.HomeSoundEffectController;
-import com.android.systemui.power.PowerUI;
-import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
-import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
-import com.android.systemui.shortcut.ShortcutKeyDispatcher;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
-import com.android.systemui.statusbar.notification.InstantAppNotifier;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.tv.TvStatusBar;
-import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel;
-import com.android.systemui.theme.ThemeOverlayController;
-import com.android.systemui.toast.ToastUI;
-import com.android.systemui.util.leak.GarbageMonitor;
-import com.android.systemui.volume.VolumeUI;
-import com.android.systemui.wmshell.WMShell;
 
-import dagger.Binds;
 import dagger.Module;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
 
 /**
  * SystemUI objects that are injectable should go here.
@@ -66,196 +31,4 @@
         KeyguardModule.class,
 })
 public abstract class SystemUIBinder {
-    /** Inject into AuthController. */
-    @Binds
-    @IntoMap
-    @ClassKey(AuthController.class)
-    public abstract CoreStartable bindAuthController(AuthController service);
-
-    /** Inject into SessionTracker. */
-    @Binds
-    @IntoMap
-    @ClassKey(SessionTracker.class)
-    public abstract CoreStartable bindSessionTracker(SessionTracker service);
-
-    /** Inject into GarbageMonitor.Service. */
-    @Binds
-    @IntoMap
-    @ClassKey(GarbageMonitor.Service.class)
-    public abstract CoreStartable bindGarbageMonitorService(GarbageMonitor.Service sysui);
-
-    /** Inject into ClipboardListener. */
-    @Binds
-    @IntoMap
-    @ClassKey(ClipboardListener.class)
-    public abstract CoreStartable bindClipboardListener(ClipboardListener sysui);
-
-    /** Inject into GlobalActionsComponent. */
-    @Binds
-    @IntoMap
-    @ClassKey(GlobalActionsComponent.class)
-    public abstract CoreStartable bindGlobalActionsComponent(GlobalActionsComponent sysui);
-
-    /** Inject into InstantAppNotifier. */
-    @Binds
-    @IntoMap
-    @ClassKey(InstantAppNotifier.class)
-    public abstract CoreStartable bindInstantAppNotifier(InstantAppNotifier sysui);
-
-    /** Inject into KeyguardViewMediator. */
-    @Binds
-    @IntoMap
-    @ClassKey(KeyguardViewMediator.class)
-    public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui);
-
-    /** Inject into KeyguardBiometricLockoutLogger. */
-    @Binds
-    @IntoMap
-    @ClassKey(KeyguardBiometricLockoutLogger.class)
-    public abstract CoreStartable bindKeyguardBiometricLockoutLogger(
-            KeyguardBiometricLockoutLogger sysui);
-
-    /** Inject into LatencyTests. */
-    @Binds
-    @IntoMap
-    @ClassKey(LatencyTester.class)
-    public abstract CoreStartable bindLatencyTester(LatencyTester sysui);
-
-    /** Inject into PowerUI. */
-    @Binds
-    @IntoMap
-    @ClassKey(PowerUI.class)
-    public abstract CoreStartable bindPowerUI(PowerUI sysui);
-
-    /** Inject into Recents. */
-    @Binds
-    @IntoMap
-    @ClassKey(Recents.class)
-    public abstract CoreStartable bindRecents(Recents sysui);
-
-    /** Inject into ScreenDecorations. */
-    @Binds
-    @IntoMap
-    @ClassKey(ScreenDecorations.class)
-    public abstract CoreStartable bindScreenDecorations(ScreenDecorations sysui);
-
-    /** Inject into ShortcutKeyDispatcher. */
-    @Binds
-    @IntoMap
-    @ClassKey(ShortcutKeyDispatcher.class)
-    public abstract CoreStartable bindsShortcutKeyDispatcher(ShortcutKeyDispatcher sysui);
-
-    /** Inject into SliceBroadcastRelayHandler. */
-    @Binds
-    @IntoMap
-    @ClassKey(SliceBroadcastRelayHandler.class)
-    public abstract CoreStartable bindSliceBroadcastRelayHandler(SliceBroadcastRelayHandler sysui);
-
-    /** Inject into StatusBar. */
-    @Binds
-    @IntoMap
-    @ClassKey(StatusBar.class)
-    public abstract CoreStartable bindsStatusBar(StatusBar sysui);
-
-    /** Inject into SystemActions. */
-    @Binds
-    @IntoMap
-    @ClassKey(SystemActions.class)
-    public abstract CoreStartable bindSystemActions(SystemActions sysui);
-
-    /** Inject into ThemeOverlayController. */
-    @Binds
-    @IntoMap
-    @ClassKey(ThemeOverlayController.class)
-    public abstract CoreStartable bindThemeOverlayController(ThemeOverlayController sysui);
-
-    /** Inject into ToastUI. */
-    @Binds
-    @IntoMap
-    @ClassKey(ToastUI.class)
-    public abstract CoreStartable bindToastUI(ToastUI service);
-
-    /** Inject into TvStatusBar. */
-    @Binds
-    @IntoMap
-    @ClassKey(TvStatusBar.class)
-    public abstract CoreStartable bindsTvStatusBar(TvStatusBar sysui);
-
-    /** Inject into TvNotificationPanel. */
-    @Binds
-    @IntoMap
-    @ClassKey(TvNotificationPanel.class)
-    public abstract CoreStartable bindsTvNotificationPanel(TvNotificationPanel sysui);
-
-    /** Inject into TvOngoingPrivacyChip. */
-    @Binds
-    @IntoMap
-    @ClassKey(TvOngoingPrivacyChip.class)
-    public abstract CoreStartable bindsTvOngoingPrivacyChip(TvOngoingPrivacyChip sysui);
-
-    /** Inject into VolumeUI. */
-    @Binds
-    @IntoMap
-    @ClassKey(VolumeUI.class)
-    public abstract CoreStartable bindVolumeUI(VolumeUI sysui);
-
-    /** Inject into WindowMagnification. */
-    @Binds
-    @IntoMap
-    @ClassKey(WindowMagnification.class)
-    public abstract CoreStartable bindWindowMagnification(WindowMagnification sysui);
-
-    /** Inject into WMShell. */
-    @Binds
-    @IntoMap
-    @ClassKey(WMShell.class)
-    public abstract CoreStartable bindWMShell(WMShell sysui);
-
-    /** Inject into HomeSoundEffectController. */
-    @Binds
-    @IntoMap
-    @ClassKey(HomeSoundEffectController.class)
-    public abstract CoreStartable bindHomeSoundEffectController(HomeSoundEffectController sysui);
-
-    /** Inject into DreamOverlay. */
-    @Binds
-    @IntoMap
-    @ClassKey(DreamOverlayRegistrant.class)
-    public abstract CoreStartable bindDreamOverlayRegistrant(
-            DreamOverlayRegistrant dreamOverlayRegistrant);
-
-    /** Inject into SmartSpaceComplication.Registrant */
-    @Binds
-    @IntoMap
-    @ClassKey(SmartSpaceComplication.Registrant.class)
-    public abstract CoreStartable bindSmartSpaceComplicationRegistrant(
-            SmartSpaceComplication.Registrant registrant);
-
-    /** Inject into MediaDreamSentinel. */
-    @Binds
-    @IntoMap
-    @ClassKey(MediaDreamSentinel.class)
-    public abstract CoreStartable bindMediaDreamSentinel(
-            MediaDreamSentinel sentinel);
-
-    /** Inject into DreamClockTimeComplication.Registrant */
-    @Binds
-    @IntoMap
-    @ClassKey(DreamClockTimeComplication.Registrant.class)
-    public abstract CoreStartable bindDreamClockTimeComplicationRegistrant(
-            DreamClockTimeComplication.Registrant registrant);
-
-    /** Inject into DreamClockDateComplication.Registrant */
-    @Binds
-    @IntoMap
-    @ClassKey(DreamClockDateComplication.Registrant.class)
-    public abstract CoreStartable bindDreamClockDateComplicationRegistrant(
-            DreamClockDateComplication.Registrant registrant);
-
-    /** Inject into DreamWeatherComplication.Registrant */
-    @Binds
-    @IntoMap
-    @ClassKey(DreamWeatherComplication.Registrant.class)
-    public abstract CoreStartable bindDreamWeatherComplicationRegistrant(
-            DreamWeatherComplication.Registrant registrant);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
new file mode 100644
index 0000000..f78929f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -0,0 +1,201 @@
+/*
+ * 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.dagger
+
+import com.android.keyguard.KeyguardBiometricLockoutLogger
+import com.android.systemui.CoreStartable
+import com.android.systemui.LatencyTester
+import com.android.systemui.ScreenDecorations
+import com.android.systemui.SliceBroadcastRelayHandler
+import com.android.systemui.accessibility.SystemActions
+import com.android.systemui.accessibility.WindowMagnification
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.keyboard.KeyboardUI
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.media.RingtonePlayer
+import com.android.systemui.power.PowerUI
+import com.android.systemui.recents.Recents
+import com.android.systemui.shortcut.ShortcutKeyDispatcher
+import com.android.systemui.statusbar.notification.InstantAppNotifier
+import com.android.systemui.theme.ThemeOverlayController
+import com.android.systemui.toast.ToastUI
+import com.android.systemui.usb.StorageNotification
+import com.android.systemui.util.NotificationChannels
+import com.android.systemui.util.leak.GarbageMonitor
+import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wmshell.WMShell
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+/**
+ * Collection of {@link CoreStartable}s that should be run on AOSP.
+ */
+@Module
+abstract class SystemUICoreStartableModule {
+    /** Inject into AuthController.  */
+    @Binds
+    @IntoMap
+    @ClassKey(AuthController::class)
+    abstract fun bindAuthController(service: AuthController): CoreStartable
+
+    /** Inject into ClipboardListener.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ClipboardListener::class)
+    abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable
+
+    /** Inject into GarbageMonitor.Service.  */
+    @Binds
+    @IntoMap
+    @ClassKey(GarbageMonitor::class)
+    abstract fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable
+
+    /** Inject into GlobalActionsComponent.  */
+    @Binds
+    @IntoMap
+    @ClassKey(GlobalActionsComponent::class)
+    abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable
+
+    /** Inject into InstantAppNotifier.  */
+    @Binds
+    @IntoMap
+    @ClassKey(InstantAppNotifier::class)
+    abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable
+
+    /** Inject into KeyboardUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyboardUI::class)
+    abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
+
+    /** Inject into KeyguardBiometricLockoutLogger */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardBiometricLockoutLogger::class)
+    abstract fun bindKeyguardBiometricLockoutLogger(
+        sysui: KeyguardBiometricLockoutLogger
+    ): CoreStartable
+
+    /** Inject into KeyguardViewMediator.  */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardViewMediator::class)
+    abstract fun bindKeyguardViewMediator(sysui: KeyguardViewMediator): CoreStartable
+
+    /** Inject into LatencyTests.  */
+    @Binds
+    @IntoMap
+    @ClassKey(LatencyTester::class)
+    abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable
+
+    /** Inject into NotificationChannels.  */
+    @Binds
+    @IntoMap
+    @ClassKey(NotificationChannels::class)
+    @PerUser
+    abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
+
+    /** Inject into PowerUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(PowerUI::class)
+    abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
+
+    /** Inject into Recents.  */
+    @Binds
+    @IntoMap
+    @ClassKey(Recents::class)
+    abstract fun bindRecents(sysui: Recents): CoreStartable
+
+    /** Inject into RingtonePlayer.  */
+    @Binds
+    @IntoMap
+    @ClassKey(RingtonePlayer::class)
+    abstract fun bind(sysui: RingtonePlayer): CoreStartable
+
+    /** Inject into ScreenDecorations.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenDecorations::class)
+    abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable
+
+    /** Inject into SessionTracker.  */
+    @Binds
+    @IntoMap
+    @ClassKey(SessionTracker::class)
+    abstract fun bindSessionTracker(service: SessionTracker): CoreStartable
+
+    /** Inject into ShortcutKeyDispatcher.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ShortcutKeyDispatcher::class)
+    abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable
+
+    /** Inject into SliceBroadcastRelayHandler.  */
+    @Binds
+    @IntoMap
+    @ClassKey(SliceBroadcastRelayHandler::class)
+    abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable
+
+    /** Inject into StorageNotification.  */
+    @Binds
+    @IntoMap
+    @ClassKey(StorageNotification::class)
+    abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
+
+    /** Inject into SystemActions.  */
+    @Binds
+    @IntoMap
+    @ClassKey(SystemActions::class)
+    abstract fun bindSystemActions(sysui: SystemActions): CoreStartable
+
+    /** Inject into ThemeOverlayController.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ThemeOverlayController::class)
+    abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
+
+    /** Inject into ToastUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ToastUI::class)
+    abstract fun bindToastUI(service: ToastUI): CoreStartable
+
+    /** Inject into VolumeUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(VolumeUI::class)
+    abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
+
+    /** Inject into WindowMagnification.  */
+    @Binds
+    @IntoMap
+    @ClassKey(WindowMagnification::class)
+    abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable
+
+    /** Inject into WMShell.  */
+    @Binds
+    @IntoMap
+    @ClassKey(WMShell::class)
+    abstract fun bindWMShell(sysui: WMShell): CoreStartable
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index a178738..a4da6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.dagger.StartStatusBarModule;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -86,6 +87,7 @@
         MediaModule.class,
         PowerModule.class,
         QSModule.class,
+        StartStatusBarModule.class,
         VolumeModule.class
 })
 public abstract class SystemUIDefaultModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java
new file mode 100644
index 0000000..f6d5ece
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger.qualifiers;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface PerUser {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 63d4d6b..a9e310d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -105,17 +105,7 @@
 
         updateUdfpsController();
         if (mUdfpsController == null) {
-            mAuthController.addCallback(new AuthController.Callback() {
-                @Override
-                public void onAllAuthenticatorsRegistered() {
-                    updateUdfpsController();
-                }
-
-                @Override
-                public void onEnrollmentsChanged() {
-                    updateUdfpsController();
-                }
-            });
+            mAuthController.addCallback(mAuthControllerCallback);
         }
     }
 
@@ -128,6 +118,11 @@
     }
 
     @Override
+    public void destroy() {
+        mAuthController.removeCallback(mAuthControllerCallback);
+    }
+
+    @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         int screenState = newState.screenState(mParameters);
         mDozeHost.cancelGentleSleep();
@@ -234,4 +229,16 @@
             mWakeLock.setAcquired(false);
         }
     }
+
+    private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+        @Override
+        public void onAllAuthenticatorsRegistered() {
+            updateUdfpsController();
+        }
+
+        @Override
+        public void onEnrollmentsChanged() {
+            updateUdfpsController();
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 77997e4..4696eed 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -122,10 +122,12 @@
 
     @Override
     public void onDestroy() {
-        mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
+        mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
         setCurrentState(Lifecycle.State.DESTROYED);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        windowManager.removeView(mWindow.getDecorView());
+        if (mWindow != null) {
+            windowManager.removeView(mWindow.getDecorView());
+        }
         mStateController.setOverlayActive(false);
         super.onDestroy();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index fa951fa..e4a7406 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.os.SystemClock
 import android.os.Trace
+import com.android.systemui.CoreStartable
 import com.android.systemui.R
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
@@ -27,6 +28,7 @@
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
+import javax.inject.Provider
 
 /**
  * Oversees SystemUI's output during bug reports (and dumpsys in general)
@@ -80,7 +82,8 @@
 class DumpHandler @Inject constructor(
     private val context: Context,
     private val dumpManager: DumpManager,
-    private val logBufferEulogizer: LogBufferEulogizer
+    private val logBufferEulogizer: LogBufferEulogizer,
+    private val startables: MutableMap<Class<*>, Provider<CoreStartable>>
 ) {
     /**
      * Dump the diagnostics! Behavior can be controlled via [args].
@@ -173,12 +176,21 @@
         pw.println("SystemUiServiceComponents configuration:")
         pw.print("vendor component: ")
         pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
-        dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents)
+        val services: MutableList<String> = startables.keys
+                .map({ cls: Class<*> -> cls.simpleName })
+                .toMutableList()
+
+        services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
+        dumpServiceList(pw, "global", services.toTypedArray())
         dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser)
     }
 
     private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) {
-        val services: Array<String>? = context.resources.getStringArray(resId)
+        val services: Array<String> = context.resources.getStringArray(resId)
+        dumpServiceList(pw, type, services)
+    }
+
+    private fun dumpServiceList(pw: PrintWriter, type: String, services: Array<String>?) {
         pw.print(type)
         pw.print(": ")
         if (services == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 51101da..1ba6e34 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -95,6 +95,10 @@
 
     /***************************************/
     // 500 - quick settings
+    /**
+     * @deprecated Not needed anymore
+     */
+    @Deprecated
     public static final BooleanFlag NEW_USER_SWITCHER =
             new BooleanFlag(500, true);
 
@@ -142,6 +146,12 @@
     public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
     public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true);
     public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, false);
+    public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
+
+    // 1000 - dock
+    public static final BooleanFlag SIMULATE_DOCK_THROUGH_CHARGING =
+            new BooleanFlag(1000, true);
+
 
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java
index f1e5b08..42230ae 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.globalactions;
 
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
-
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
@@ -33,15 +31,9 @@
  * ConstraintLayout implementation of the button layout created by the global actions dialog.
  */
 public class GlobalActionsLayoutLite extends GlobalActionsLayout {
-    private final int mMaxColumns;
-    private final int mMaxRows;
 
     public GlobalActionsLayoutLite(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mMaxColumns = getResources().getInteger(
-                com.android.systemui.R.integer.power_menu_lite_max_columns);
-        mMaxRows = getResources().getInteger(
-                com.android.systemui.R.integer.power_menu_lite_max_rows);
         setOnClickListener(v -> { }); // Prevent parent onClickListener from triggering
     }
 
@@ -60,10 +52,13 @@
     @Override
     public void onUpdateList() {
         super.onUpdateList();
-        int nElementsWrap = (getCurrentRotation() == ROTATION_NONE) ? mMaxColumns : mMaxRows;
+        int nElementsWrap = getResources().getInteger(
+                com.android.systemui.R.integer.power_menu_lite_max_columns);
         int nChildren = getListView().getChildCount() - 1; // don't count flow element
-        if (getCurrentRotation() != ROTATION_NONE && nChildren > mMaxRows) {
-            // up to 4 elements can fit in a row in landscape, otherwise limit for balance
+
+        // Avoid having just one action on the last row if there are more than 2 columns because
+        // it looks unbalanced. Instead, bring the column size down to balance better.
+        if (nChildren == nElementsWrap + 1 && nElementsWrap > 2) {
             nElementsWrap -= 1;
         }
         Flow flow = findViewById(R.id.list_flow);
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 1c0b104..6f7e73fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -52,6 +52,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -60,6 +61,10 @@
 import java.util.List;
 import java.util.Set;
 
+import javax.inject.Inject;
+
+/** */
+@SysUISingleton
 public class KeyboardUI extends CoreStartable implements InputManager.OnTabletModeChangedListener {
     private static final String TAG = "KeyboardUI";
     private static final boolean DEBUG = false;
@@ -117,6 +122,7 @@
 
     private int mState;
 
+    @Inject
     public KeyboardUI(Context context) {
         super(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 88555ed..b96eee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -102,7 +102,7 @@
             "persist.wm.enable_remote_keyguard_animation";
 
     private static final int sEnableRemoteKeyguardAnimation =
-            SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
+            SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
 
     /**
      * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0f08a18..ae7147e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -789,9 +789,10 @@
         @Override
         public int getBouncerPromptReason() {
             int currentUser = KeyguardUpdateMonitor.getCurrentUser();
-            boolean trust = mUpdateMonitor.isTrustUsuallyManaged(currentUser);
-            boolean biometrics = mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser);
-            boolean any = trust || biometrics;
+            boolean trustAgentsEnabled = mUpdateMonitor.isTrustUsuallyManaged(currentUser);
+            boolean biometricsEnrolled =
+                    mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser);
+            boolean any = trustAgentsEnabled || biometricsEnrolled;
             KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
                     mUpdateMonitor.getStrongAuthTracker();
             int strongAuth = strongAuthTracker.getStrongAuthForUser(currentUser);
@@ -800,9 +801,10 @@
                 return KeyguardSecurityView.PROMPT_REASON_RESTART;
             } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-            } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) {
+            } else if ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
-            } else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
+            } else if (trustAgentsEnabled
+                    && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
             } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
                     || mUpdateMonitor.isFingerprintLockedOut())) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index b3e6682..ce7a697 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -371,13 +371,6 @@
         // Output switcher chip
         ViewGroup seamlessView = mMediaViewHolder.getSeamless();
         seamlessView.setVisibility(View.VISIBLE);
-        seamlessView.setOnClickListener(
-                v -> {
-                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                        mMediaOutputDialogFactory.create(data.getPackageName(), true,
-                                mMediaViewHolder.getSeamlessButton());
-                    }
-                });
         ImageView iconView = mMediaViewHolder.getSeamlessIcon();
         TextView deviceName = mMediaViewHolder.getSeamlessText();
         final MediaDeviceData device = data.getDevice();
@@ -387,8 +380,8 @@
         final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
         mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha);
         seamlessView.setEnabled(!seamlessDisabled);
-        String deviceString = null;
-        if (device != null && device.getEnabled()) {
+        CharSequence deviceString = mContext.getString(R.string.media_seamless_other_device);
+        if (device != null) {
             Drawable icon = device.getIcon();
             if (icon instanceof AdaptiveIcon) {
                 AdaptiveIcon aIcon = (AdaptiveIcon) icon;
@@ -399,13 +392,32 @@
             }
             deviceString = device.getName();
         } else {
-            // Reset to default
-            Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip.");
+            // Set to default icon
             iconView.setImageResource(R.drawable.ic_media_home_devices);
-            deviceString =  mContext.getString(R.string.media_seamless_other_device);
         }
         deviceName.setText(deviceString);
         seamlessView.setContentDescription(deviceString);
+        seamlessView.setOnClickListener(
+                v -> {
+                    if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                        return;
+                    }
+                    if (device.getIntent() != null) {
+                        if (device.getIntent().isActivity()) {
+                            mActivityStarter.startActivity(
+                                    device.getIntent().getIntent(), true);
+                        } else {
+                            try {
+                                device.getIntent().send();
+                            } catch (PendingIntent.CanceledException e) {
+                                Log.e(TAG, "Device pending intent was canceled");
+                            }
+                        }
+                    } else {
+                        mMediaOutputDialogFactory.create(data.getPackageName(), true,
+                                mMediaViewHolder.getSeamlessButton());
+                    }
+            });
 
         // Dismiss
         mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 4b8dfde..500e82e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -164,8 +164,17 @@
 )
 
 /** State of the media device. */
-data class MediaDeviceData(
+data class MediaDeviceData
+@JvmOverloads constructor(
+    /** Whether or not to enable the chip */
     val enabled: Boolean,
+
+    /** Device icon to show in the chip */
     val icon: Drawable?,
-    val name: String?
+
+    /** Device display name */
+    val name: CharSequence?,
+
+    /** Optional intent to override the default output switcher for this control */
+    val intent: PendingIntent? = null
 )
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 240ca36..e1ff110 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -27,8 +27,6 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
 import android.graphics.Bitmap
 import android.graphics.ImageDecoder
 import android.graphics.drawable.Icon
@@ -129,7 +127,7 @@
     private val useQsMediaPlayer: Boolean,
     private val systemClock: SystemClock,
     private val tunerService: TunerService,
-    private val mediaFlags: MediaFlags,
+    private val mediaFlags: MediaFlags
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -170,20 +168,9 @@
 
     /**
      * Check whether this notification is an RCN
-     * TODO(b/204910409) implement new API for explicitly declaring this
      */
     private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
-        val pm = context.packageManager
-        try {
-            val info = pm.getApplicationInfo(sbn.packageName, PackageManager.MATCH_SYSTEM_ONLY)
-            if (info.privateFlags and ApplicationInfo.PRIVATE_FLAG_PRIVILEGED != 0) {
-                val extras = sbn.notification.extras
-                if (extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
-                    return true
-                }
-            }
-        } catch (e: PackageManager.NameNotFoundException) { }
-        return false
+        return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
     }
 
     @Inject
@@ -597,6 +584,25 @@
             artist = HybridGroupManager.resolveText(notif)
         }
 
+        // Device name (used for remote cast notifications)
+        var device: MediaDeviceData? = null
+        if (isRemoteCastNotification(sbn)) {
+            val extras = sbn.notification.extras
+            val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
+            val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
+            val deviceIntent = extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT)
+                    as PendingIntent?
+            Log.d(TAG, "$key is RCN for $deviceName")
+
+            if (deviceName != null && deviceIcon > -1) {
+                // Name and icon must be present, but intent may be null
+                val enabled = deviceIntent != null && deviceIntent.isActivity
+                val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon)
+                        .loadDrawable(sbn.getPackageContext(context))
+                device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent)
+            }
+        }
+
         // Control buttons
         // If flag is enabled and controller has a PlaybackState, create actions from session info
         // Otherwise, use the notification actions
@@ -624,7 +630,7 @@
             val active = mediaEntries[key]?.active ?: true
             onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
                     smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
-                    semanticActions, sbn.packageName, token, notif.contentIntent, null,
+                    semanticActions, sbn.packageName, token, notif.contentIntent, device,
                     active, resumeAction = resumeAction, playbackLocation = playbackLocation,
                     notificationKey = key, hasCheckedForResume = hasCheckedForResume,
                     isPlaying = isPlaying, isClearable = sbn.isClearable(),
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index bed254f..ffae898 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media
 
+import android.graphics.drawable.Drawable
 import android.media.MediaRouter2Manager
 import android.media.session.MediaController
 import androidx.annotation.AnyThread
@@ -27,6 +28,8 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.Executor
@@ -41,6 +44,7 @@
     private val controllerFactory: MediaControllerFactory,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
     private val mr2manager: MediaRouter2Manager,
+    private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory,
     @Main private val fgExecutor: Executor,
     @Background private val bgExecutor: Executor,
     dumpManager: DumpManager
@@ -77,11 +81,25 @@
         var entry = entries[key]
         if (entry == null || entry?.token != data.token) {
             entry?.stop()
+            if (data.device != null) {
+                // If we were already provided device info (e.g. from RCN), keep that and don't
+                // listen for updates, but process once to push updates to listeners
+                processDevice(key, oldKey, data.device)
+                return
+            }
             val controller = data.token?.let {
                 controllerFactory.create(it)
             }
-            entry = Entry(key, oldKey, controller,
-                    localMediaManagerFactory.create(data.packageName))
+            val localMediaManager = localMediaManagerFactory.create(data.packageName)
+            val muteAwaitConnectionManager =
+                    muteAwaitConnectionManagerFactory.create(localMediaManager)
+            entry = Entry(
+                key,
+                oldKey,
+                controller,
+                localMediaManager,
+                muteAwaitConnectionManager
+            )
             entries[key] = entry
             entry.start()
         }
@@ -126,7 +144,8 @@
         val key: String,
         val oldKey: String?,
         val controller: MediaController?,
-        val localMediaManager: LocalMediaManager
+        val localMediaManager: LocalMediaManager,
+        val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
     ) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
 
         val token
@@ -142,11 +161,15 @@
                     }
                 }
             }
+        // A device that is not yet connected but is expected to connect imminently. Because it's
+        // expected to connect imminently, it should be displayed as the current device.
+        private var aboutToConnectDeviceOverride: MediaDeviceData? = null
 
         @AnyThread
         fun start() = bgExecutor.execute {
             localMediaManager.registerCallback(this)
             localMediaManager.startScan()
+            muteAwaitConnectionManager?.startListening()
             playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
             controller?.registerCallback(this)
             updateCurrent()
@@ -159,6 +182,7 @@
             controller?.unregisterCallback(this)
             localMediaManager.stopScan()
             localMediaManager.unregisterCallback(this)
+            muteAwaitConnectionManager?.stopListening()
         }
 
         fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
@@ -197,8 +221,21 @@
             }
         }
 
+        override fun onAboutToConnectDeviceChanged(deviceName: String?, deviceIcon: Drawable?) {
+            aboutToConnectDeviceOverride = if (deviceName == null || deviceIcon == null) {
+                null
+            } else {
+                MediaDeviceData(enabled = true, deviceIcon, deviceName)
+            }
+            updateCurrent()
+        }
+
         @WorkerThread
         private fun updateCurrent() {
+            if (aboutToConnectDeviceOverride != null) {
+                current = aboutToConnectDeviceOverride
+                return
+            }
             val device = localMediaManager.currentConnectedDevice
             val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
index b9795f1..e146768 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
@@ -37,4 +37,9 @@
         return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS) &&
             featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT)
     }
-}
\ No newline at end of file
+
+    /**
+     * Check whether we support displaying information about mute await connections.
+     */
+    fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index ae5f9b6..4e35d16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -38,16 +38,20 @@
 import android.util.Log;
 
 import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.HashMap;
 
+import javax.inject.Inject;
+
 /**
  * Service that offers to play ringtones by {@link Uri}, since our process has
  * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
  */
+@SysUISingleton
 public class RingtonePlayer extends CoreStartable {
     private static final String TAG = "RingtonePlayer";
     private static final boolean LOGD = false;
@@ -59,6 +63,7 @@
     private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
     private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
 
+    @Inject
     public RingtonePlayer(Context context) {
         super(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index f8b34f9..3225f73 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -18,15 +18,18 @@
 
 import android.app.Service;
 import android.content.Context;
+import android.os.Handler;
 import android.view.WindowManager;
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.MediaFlags;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.media.MediaHostStatesManager;
 import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
 import com.android.systemui.media.nearby.NearbyMediaDevicesService;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
@@ -117,12 +120,14 @@
             MediaTttFlags mediaTttFlags,
             CommandQueue commandQueue,
             Context context,
-            WindowManager windowManager) {
+            WindowManager windowManager,
+            @Main Handler mainHandler) {
         if (!mediaTttFlags.isMediaTttEnabled()) {
             return Optional.empty();
         }
         return Optional.of(
-                new MediaTttChipControllerReceiver(commandQueue, context, windowManager));
+                new MediaTttChipControllerReceiver(
+                        commandQueue, context, windowManager, mainHandler));
     }
 
     /** */
@@ -140,6 +145,20 @@
                 new MediaTttCommandLineHelper(commandRegistry, context, mainExecutor));
     }
 
+    /** */
+    @Provides
+    @SysUISingleton
+    static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli(
+            MediaFlags mediaFlags,
+            CommandRegistry commandRegistry,
+            Context context
+    ) {
+        if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
+            return Optional.empty();
+        }
+        return Optional.of(new MediaMuteAwaitConnectionCli(commandRegistry, context));
+    }
+
     /** Inject into NearbyMediaDevicesService. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index a9e9f0f..8731a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -153,6 +153,10 @@
 
     @VisibleForTesting
     void refresh() {
+        refresh(false);
+    }
+
+    void refresh(boolean deviceSetChanged) {
         // Update header icon
         final int iconRes = getHeaderIconRes();
         final IconCompat iconCompat = getHeaderIcon();
@@ -190,7 +194,8 @@
         }
         if (!mAdapter.isDragging() && !mAdapter.isAnimating()) {
             int currentActivePosition = mAdapter.getCurrentActivePosition();
-            if (currentActivePosition >= 0 && currentActivePosition < mAdapter.getItemCount()) {
+            if (!deviceSetChanged && currentActivePosition >= 0
+                    && currentActivePosition < mAdapter.getItemCount()) {
                 mAdapter.notifyItemChanged(currentActivePosition);
             } else {
                 mAdapter.notifyDataSetChanged();
@@ -232,6 +237,11 @@
     }
 
     @Override
+    public void onDeviceListChanged() {
+        mMainThreadHandler.post(() -> refresh(true));
+    }
+
+    @Override
     public void dismissDialog() {
         dismiss();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 2caecf2..4961711 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -171,7 +171,7 @@
     @Override
     public void onDeviceListUpdate(List<MediaDevice> devices) {
         buildMediaDevices(devices);
-        mCallback.onRouteChanged();
+        mCallback.onDeviceListChanged();
     }
 
     @Override
@@ -570,11 +570,16 @@
         void onMediaStoppedOrPaused();
 
         /**
-         * Override to handle the device updating.
+         * Override to handle the device status or attributes updating.
          */
         void onRouteChanged();
 
         /**
+         * Override to handle the devices set updating.
+         */
+        void onDeviceListChanged();
+
+        /**
          * Override to dismiss dialog.
          */
         void dismissDialog();
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
new file mode 100644
index 0000000..2ae3a63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.muteawait
+
+import android.content.Context
+import android.media.AudioAttributes.USAGE_MEDIA
+import android.media.AudioDeviceAttributes
+import android.media.AudioManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import java.io.PrintWriter
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/** A command line interface to manually test [MediaMuteAwaitConnectionManager]. */
+@SysUISingleton
+class MediaMuteAwaitConnectionCli @Inject constructor(
+    commandRegistry: CommandRegistry,
+    private val context: Context
+) {
+    init {
+        commandRegistry.registerCommand(MEDIA_MUTE_AWAIT_COMMAND) { MuteAwaitCommand() }
+    }
+
+    inner class MuteAwaitCommand : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            val device = AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                /* type= */ Integer.parseInt(args[0]),
+                ADDRESS,
+                /* name= */ args[1],
+                listOf(),
+                listOf(),
+            )
+            val startOrCancel = args[2]
+
+            val audioManager: AudioManager =
+                context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+            when (startOrCancel) {
+                START ->
+                    audioManager.muteAwaitConnection(
+                            intArrayOf(USAGE_MEDIA), device, TIMEOUT, TIMEOUT_UNITS
+                    )
+                CANCEL -> audioManager.cancelMuteAwaitConnection(device)
+                else -> pw.println("Must specify `$START` or `$CANCEL`; was $startOrCancel")
+            }
+        }
+        override fun help(pw: PrintWriter) {
+            pw.println("Usage: adb shell cmd statusbar $MEDIA_MUTE_AWAIT_COMMAND " +
+                    "[type] [name] [$START|$CANCEL]")
+        }
+    }
+}
+
+private const val MEDIA_MUTE_AWAIT_COMMAND = "media-mute-await"
+private const val START = "start"
+private const val CANCEL = "cancel"
+private const val ADDRESS = "address"
+private const val TIMEOUT = 5L
+private val TIMEOUT_UNITS = TimeUnit.SECONDS
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
new file mode 100644
index 0000000..22bc557
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.muteawait
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.media.AudioAttributes.USAGE_MEDIA
+import android.media.AudioDeviceAttributes
+import android.media.AudioManager
+import com.android.settingslib.media.DeviceIconUtil
+import com.android.settingslib.media.LocalMediaManager
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+
+/**
+ * A class responsible for keeping track of devices that have muted audio playback until the device
+ * is connected. The device connection expected to happen imminently, so we'd like to display the
+ * device name in the media player. When the about-to-connect device changes, [localMediaManager]
+ * will be notified.
+ *
+ * See [AudioManager.muteAwaitConnection] and b/206614671 for more details.
+ *
+ * TODO(b/206614671): Add logging.
+ */
+class MediaMuteAwaitConnectionManager constructor(
+    @Main private val mainExecutor: Executor,
+    private val localMediaManager: LocalMediaManager,
+    private val context: Context,
+    private val deviceIconUtil: DeviceIconUtil
+) {
+    var currentMutedDevice: AudioDeviceAttributes? = null
+
+    val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+
+    val muteAwaitConnectionChangeListener = object : AudioManager.MuteAwaitConnectionCallback() {
+        override fun onMutedUntilConnection(device: AudioDeviceAttributes, mutedUsages: IntArray) {
+            if (USAGE_MEDIA in mutedUsages) {
+                // There should only be one device that's mutedUntilConnection at a time, so we can
+                // safely override any previous value.
+                currentMutedDevice = device
+                localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, device.getIcon())
+            }
+        }
+
+        override fun onUnmutedEvent(
+            @UnmuteEvent unmuteEvent: Int,
+            device: AudioDeviceAttributes,
+            mutedUsages: IntArray
+        ) {
+            if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) {
+                currentMutedDevice = null
+                localMediaManager.dispatchAboutToConnectDeviceChanged(null, null)
+            }
+        }
+    }
+
+    /** Start listening for mute await events. */
+    fun startListening() {
+        audioManager.registerMuteAwaitConnectionCallback(
+                mainExecutor, muteAwaitConnectionChangeListener
+        )
+        val currentDevice = audioManager.mutingExpectedDevice
+        if (currentDevice != null) {
+            currentMutedDevice = currentDevice
+            localMediaManager.dispatchAboutToConnectDeviceChanged(
+                currentDevice.name, currentDevice.getIcon()
+            )
+        }
+    }
+
+    /** Stop listening for mute await events. */
+    fun stopListening() {
+        audioManager.unregisterMuteAwaitConnectionCallback(muteAwaitConnectionChangeListener)
+    }
+
+    private fun AudioDeviceAttributes.getIcon(): Drawable {
+        return deviceIconUtil.getIconFromAudioDeviceType(this.type, context)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
new file mode 100644
index 0000000..118b2dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.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.systemui.media.muteawait
+
+import android.content.Context
+import com.android.settingslib.media.DeviceIconUtil
+import com.android.settingslib.media.LocalMediaManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.MediaFlags
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/** Factory class to create [MediaMuteAwaitConnectionManager] instances. */
+@SysUISingleton
+class MediaMuteAwaitConnectionManagerFactory @Inject constructor(
+    private val mediaFlags: MediaFlags,
+    private val context: Context,
+    @Main private val mainExecutor: Executor
+) {
+    private val deviceIconUtil = DeviceIconUtil()
+
+    /** Creates a [MediaMuteAwaitConnectionManager]. */
+    fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager? {
+        if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
+            return null
+        }
+        return MediaMuteAwaitConnectionManager(
+                mainExecutor, localMediaManager, context, deviceIconUtil
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 26f31cd..9dd8222 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -78,6 +78,7 @@
         override fun execute(pw: PrintWriter, args: List<String>) {
             val routeInfo = MediaRoute2Info.Builder("id", args[0])
                     .addFeature("feature")
+                    .setPackageName(TEST_PACKAGE_NAME)
                     .build()
 
             val commandName = args[1]
@@ -137,16 +138,25 @@
         override fun execute(pw: PrintWriter, args: List<String>) {
             val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
                     as StatusBarManager
+            val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
+                .addFeature("feature")
+                .setPackageName(TEST_PACKAGE_NAME)
+                .build()
+
             when(val commandName = args[0]) {
                 CLOSE_TO_SENDER_STATE ->
                     statusBarManager.updateMediaTapToTransferReceiverDisplay(
                         StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
-                        routeInfo
+                        routeInfo,
+                        null,
+                        null
                     )
                 FAR_FROM_SENDER_STATE ->
                     statusBarManager.updateMediaTapToTransferReceiverDisplay(
                         StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
-                        routeInfo
+                        routeInfo,
+                        null,
+                        null
                     )
                 else ->
                     pw.println("Invalid command name $commandName")
@@ -170,7 +180,4 @@
 @VisibleForTesting
 const val FAR_FROM_SENDER_STATE = "FarFromSender"
 private const val CLI_TAG = "MediaTransferCli"
-
-private val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
-    .addFeature("feature")
-    .build()
\ No newline at end of file
+private const val TEST_PACKAGE_NAME = "com.android.systemui"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 2ed2f4f..4993105 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -22,6 +22,7 @@
 import android.graphics.PixelFormat
 import android.view.Gravity
 import android.view.LayoutInflater
+import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
 import com.android.internal.widget.CachingIconView
@@ -35,7 +36,7 @@
  * gets displayed to the user.
  */
 abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
-    private val context: Context,
+    internal val context: Context,
     private val windowManager: WindowManager,
     @LayoutRes private val chipLayoutRes: Int
 ) {
@@ -100,10 +101,17 @@
      * This is in the common superclass since both the sender and the receiver show an icon.
      */
     internal fun setIcon(chipState: T, currentChipView: ViewGroup) {
-        currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply {
-            this.setImageDrawable(chipState.appIconDrawable)
-            this.contentDescription = chipState.appIconContentDescription
+        val appIconView = currentChipView.requireViewById<CachingIconView>(R.id.app_icon)
+        appIconView.contentDescription = chipState.getAppName(context)
+
+        val appIcon = chipState.getAppIcon(context)
+        val visibility = if (appIcon != null) {
+            View.VISIBLE
+        } else {
+            View.GONE
         }
+        appIconView.setImageDrawable(appIcon)
+        appIconView.visibility = visibility
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
index c510cbb..2da48ce 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
@@ -16,15 +16,42 @@
 
 package com.android.systemui.media.taptotransfer.common
 
+import android.content.Context
+import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
+import android.util.Log
 
 /**
  * A superclass chip state that will be subclassed by the sender chip and receiver chip.
  *
- * @property appIconDrawable a drawable representing the icon of the app playing the media.
- * @property appIconContentDescription a string to use as the content description for the icon.
+ * @property appPackageName the package name of the app playing the media. Will be used to fetch the
+ *   app icon and app name.
  */
 open class MediaTttChipState(
-    internal val appIconDrawable: Drawable,
-    internal val appIconContentDescription: String
-)
+    internal val appPackageName: String?,
+) {
+    open fun getAppIcon(context: Context): Drawable? {
+        appPackageName ?: return null
+        return try {
+            context.packageManager.getApplicationIcon(appPackageName)
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.w(TAG, "Cannot find icon for package $appPackageName", e)
+            null
+        }
+    }
+
+    /** Returns the name of the app playing the media or null if we can't find it. */
+    open fun getAppName(context: Context): String? {
+        appPackageName ?: return null
+        return try {
+            context.packageManager.getApplicationInfo(
+                appPackageName, PackageManager.ApplicationInfoFlags.of(0)
+            ).loadLabel(context.packageManager).toString()
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.w(TAG, "Cannot find name for package $appPackageName", e)
+            null
+        }
+    }
+}
+
+private val TAG = MediaTttChipState::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
index df6b934..6a4b62a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
@@ -16,14 +16,35 @@
 
 package com.android.systemui.media.taptotransfer.receiver
 
+import android.content.Context
 import android.graphics.drawable.Drawable
 import com.android.systemui.media.taptotransfer.common.MediaTttChipState
 
 /**
  * A class that stores all the information necessary to display the media tap-to-transfer chip on
  * the receiver device.
+ *
+ * @property appIconDrawable a drawable representing the icon of the app playing the media. If
+ *     present, this will be used in [this.getAppIcon] instead of [appPackageName].
+ * @property appName a name for the app playing the media. If present, this will be used in
+ *     [this.getAppName] instead of [appPackageName].
  */
 class ChipStateReceiver(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String
-) : MediaTttChipState(appIconDrawable, appIconContentDescription)
+    appPackageName: String?,
+    private val appIconDrawable: Drawable?,
+    private val appName: CharSequence?
+) : MediaTttChipState(appPackageName) {
+    override fun getAppIcon(context: Context): Drawable? {
+        if (appIconDrawable != null) {
+            return appIconDrawable
+        }
+        return super.getAppIcon(context)
+    }
+
+    override fun getAppName(context: Context): String? {
+        if (appName != null) {
+            return appName.toString()
+        }
+        return super.getAppName(context)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 2d3ca5f..18623c0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -18,14 +18,17 @@
 
 import android.app.StatusBarManager
 import android.content.Context
-import android.graphics.Color
+import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.media.MediaRoute2Info
+import android.os.Handler
 import android.util.Log
 import android.view.ViewGroup
 import android.view.WindowManager
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
 import com.android.systemui.statusbar.CommandQueue
 import javax.inject.Inject
@@ -40,22 +43,19 @@
     commandQueue: CommandQueue,
     context: Context,
     windowManager: WindowManager,
+    @Main private val mainHandler: Handler,
 ) : MediaTttChipControllerCommon<ChipStateReceiver>(
     context, windowManager, R.layout.media_ttt_chip_receiver
 ) {
-    // TODO(b/216141279): Use app icon from media route info instead of this fake one.
-    private val fakeAppIconDrawable =
-        Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
-            it.setTint(Color.YELLOW)
-        }
-
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferReceiverDisplay(
             @StatusBarManager.MediaTransferReceiverState displayState: Int,
-            routeInfo: MediaRoute2Info
+            routeInfo: MediaRoute2Info,
+            appIcon: Icon?,
+            appName: CharSequence?
         ) {
             this@MediaTttChipControllerReceiver.updateMediaTapToTransferReceiverDisplay(
-                displayState, routeInfo
+                displayState, routeInfo, appIcon, appName
             )
         }
     }
@@ -66,11 +66,28 @@
 
     private fun updateMediaTapToTransferReceiverDisplay(
         @StatusBarManager.MediaTransferReceiverState displayState: Int,
-        routeInfo: MediaRoute2Info
+        routeInfo: MediaRoute2Info,
+        appIcon: Icon?,
+        appName: CharSequence?
     ) {
         when(displayState) {
-            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER ->
-                displayChip(ChipStateReceiver(fakeAppIconDrawable, routeInfo.name.toString()))
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> {
+                val packageName = routeInfo.packageName
+                if (appIcon == null) {
+                    displayChip(ChipStateReceiver(packageName, null, appName))
+                } else {
+                    appIcon.loadDrawableAsync(
+                        context,
+                        Icon.OnDrawableLoadedListener { drawable ->
+                            displayChip(
+                                ChipStateReceiver(packageName, drawable, appName)
+                            )},
+                        // Notify the listener on the main handler since the listener will update
+                        // the UI.
+                        mainHandler
+                    )
+                }
+            }
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER -> removeChip()
             else ->
                 Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
@@ -82,4 +99,4 @@
     }
 }
 
-private const val RECEIVER_TAG = "MediaTapToTransferReceiver"
+private const val RECEIVER_TAG = "MediaTapToTransferRcvr"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 05baf78..9b537fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.media.taptotransfer.sender
 
 import android.content.Context
-import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
@@ -31,9 +30,8 @@
  * contain additional information that is necessary for only that state.
  */
 sealed class ChipStateSender(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String
-) : MediaTttChipState(appIconDrawable, appIconContentDescription) {
+    appPackageName: String?
+) : MediaTttChipState(appPackageName) {
     /** Returns a fully-formed string with the text that the chip should display. */
     abstract fun getChipTextString(context: Context): String
 
@@ -60,10 +58,9 @@
  * @property otherDeviceName the name of the other device involved in the transfer.
  */
 class AlmostCloseToStartCast(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String,
+    appPackageName: String?,
     private val otherDeviceName: String,
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName)
     }
@@ -77,10 +74,9 @@
  * @property otherDeviceName the name of the other device involved in the transfer.
  */
 class AlmostCloseToEndCast(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String,
+    appPackageName: String?,
     private val otherDeviceName: String,
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName)
     }
@@ -93,10 +89,9 @@
  * @property otherDeviceName the name of the other device involved in the transfer.
  */
 class TransferToReceiverTriggered(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String,
+    appPackageName: String?,
     private val otherDeviceName: String
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
     }
@@ -109,9 +104,8 @@
  * sender) has been initiated (but not completed).
  */
 class TransferToThisDeviceTriggered(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    appPackageName: String?,
+) : ChipStateSender(appPackageName) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_transfer_playing_this_device)
     }
@@ -127,11 +121,10 @@
  *   undo button. The undo button will only be shown if this is non-null.
  */
 class TransferToReceiverSucceeded(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String,
+    appPackageName: String?,
     private val otherDeviceName: String,
     val undoCallback: IUndoMediaTransferCallback? = null
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
     }
@@ -149,10 +142,7 @@
             // but that may take too long to go through the binder and the user may be confused as
             // to why the UI hasn't changed yet. So, we immediately change the UI here.
             controllerSender.displayChip(
-                TransferToThisDeviceTriggered(
-                    this.appIconDrawable,
-                    this.appIconContentDescription
-                )
+                TransferToThisDeviceTriggered(this.appPackageName)
             )
         }
     }
@@ -166,11 +156,10 @@
  *   undo button. The undo button will only be shown if this is non-null.
  */
 class TransferToThisDeviceSucceeded(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String,
+    appPackageName: String?,
     private val otherDeviceName: String,
     val undoCallback: IUndoMediaTransferCallback? = null
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+) : ChipStateSender(appPackageName) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_transfer_playing_this_device)
     }
@@ -189,8 +178,7 @@
             // to why the UI hasn't changed yet. So, we immediately change the UI here.
             controllerSender.displayChip(
                 TransferToReceiverTriggered(
-                    this.appIconDrawable,
-                    this.appIconContentDescription,
+                    this.appPackageName,
                     this.otherDeviceName
                 )
             )
@@ -200,9 +188,8 @@
 
 /** A state representing that a transfer has failed. */
 class TransferFailed(
-    appIconDrawable: Drawable,
-    appIconContentDescription: String
-) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    appPackageName: String?,
+) : ChipStateSender(appPackageName) {
     override fun getChipTextString(context: Context): String {
         return context.getString(R.string.media_transfer_failed)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index d1790d2..da767ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -18,8 +18,6 @@
 
 import android.app.StatusBarManager
 import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.Icon
 import android.media.MediaRoute2Info
 import android.util.Log
 import android.view.View
@@ -45,12 +43,6 @@
 ) : MediaTttChipControllerCommon<ChipStateSender>(
     context, windowManager, R.layout.media_ttt_chip
 ) {
-    // TODO(b/216141276): Use app icon from media route info instead of this fake one.
-    private val fakeAppIconDrawable =
-        Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
-            it.setTint(Color.YELLOW)
-        }
-
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferSenderDisplay(
                 @StatusBarManager.MediaTransferSenderState displayState: Int,
@@ -72,46 +64,24 @@
         routeInfo: MediaRoute2Info,
         undoCallback: IUndoMediaTransferCallback?
     ) {
-        // TODO(b/217418566): This app icon content description is incorrect --
-        //   routeInfo.name is the name of the device, not the name of the app.
-        val appIconContentDescription = routeInfo.name.toString()
+        val appPackageName = routeInfo.packageName
         val otherDeviceName = routeInfo.name.toString()
         val chipState = when(displayState) {
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST ->
-                AlmostCloseToStartCast(
-                    fakeAppIconDrawable, appIconContentDescription, otherDeviceName
-                )
+                AlmostCloseToStartCast(appPackageName, otherDeviceName)
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST ->
-                AlmostCloseToEndCast(
-                    fakeAppIconDrawable, appIconContentDescription, otherDeviceName
-                )
+                AlmostCloseToEndCast(appPackageName, otherDeviceName)
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED ->
-                TransferToReceiverTriggered(
-                    fakeAppIconDrawable, appIconContentDescription, otherDeviceName
-                )
+                TransferToReceiverTriggered(appPackageName, otherDeviceName)
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED ->
-                TransferToThisDeviceTriggered(
-                    fakeAppIconDrawable, appIconContentDescription
-                )
+                TransferToThisDeviceTriggered(appPackageName)
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED ->
-                TransferToReceiverSucceeded(
-                    fakeAppIconDrawable,
-                    appIconContentDescription,
-                    otherDeviceName,
-                    undoCallback
-                )
+                TransferToReceiverSucceeded(appPackageName, otherDeviceName, undoCallback)
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED ->
-                TransferToThisDeviceSucceeded(
-                    fakeAppIconDrawable,
-                    appIconContentDescription,
-                    otherDeviceName,
-                    undoCallback
-                )
+                TransferToThisDeviceSucceeded(appPackageName, otherDeviceName, undoCallback)
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED ->
-                TransferFailed(
-                    fakeAppIconDrawable, appIconContentDescription
-                )
+                TransferFailed(appPackageName)
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER -> {
                 removeChip()
                 null
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 98b49b1..aa1117c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -31,6 +31,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -215,9 +216,11 @@
     /** @return {@code true} if taskbar is enabled, false otherwise */
     private boolean initializeTaskbarIfNecessary() {
         if (mIsTablet) {
+            Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
             // Remove navigation bar when taskbar is showing
             removeNavigationBar(mContext.getDisplayId());
             mTaskbarDelegate.init(mContext.getDisplayId());
+            Trace.endSection();
         } else {
             mTaskbarDelegate.destroy();
         }
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 be45a62..9199911 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -263,11 +263,14 @@
                     // Notify FalsingManager that an intentional gesture has occurred.
                     // TODO(b/186519446): use a different method than isFalseTouch
                     mFalsingManager.isFalseTouch(BACK_GESTURE);
-                    boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
-                    boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-                    if (DEBUG_MISSING_GESTURE) {
-                        Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + sendDown
-                                + ", up=" + sendUp);
+                    // Only inject back keycodes when ahead-of-time back dispatching is disabled.
+                    if (mBackAnimation == null) {
+                        boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+                        boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+                        if (DEBUG_MISSING_GESTURE) {
+                            Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down="
+                                    + sendDown + ", up=" + sendUp);
+                        }
                     }
 
                     mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index c18209d..4da574d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -43,6 +43,7 @@
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.window.BackEvent;
 
 import androidx.core.graphics.ColorUtils;
 import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -464,7 +465,8 @@
     @Override
     public void onMotionEvent(MotionEvent event) {
         if (mBackAnimation != null) {
-            mBackAnimation.onBackMotion(event);
+            mBackAnimation.onBackMotion(
+                    event, mIsLeftPanel ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
         }
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
deleted file mode 100644
index 18d28bf..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.ListAdapter;
-
-import com.android.systemui.R;
-
-/**
- * Similar to a ListView, but it will show only as many items as fit on screen and
- * bind those instead of scrolling.
- */
-public class AutoSizingList extends LinearLayout {
-
-    private static final String TAG = "AutoSizingList";
-    private final int mItemSize;
-    private final Handler mHandler;
-
-    @Nullable
-    private ListAdapter mAdapter;
-    private int mCount;
-    private boolean mEnableAutoSizing;
-
-    public AutoSizingList(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-
-        mHandler = new Handler();
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoSizingList);
-        mItemSize = a.getDimensionPixelSize(R.styleable.AutoSizingList_itemHeight, 0);
-        mEnableAutoSizing = a.getBoolean(R.styleable.AutoSizingList_enableAutoSizing, true);
-        a.recycle();
-    }
-
-    public void setAdapter(ListAdapter adapter) {
-        if (mAdapter != null) {
-            mAdapter.unregisterDataSetObserver(mDataObserver);
-        }
-        mAdapter = adapter;
-        if (adapter != null) {
-            adapter.registerDataSetObserver(mDataObserver);
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int requestedHeight = MeasureSpec.getSize(heightMeasureSpec);
-        if (requestedHeight != 0) {
-            int count = getItemCount(requestedHeight);
-            if (mCount != count) {
-                postRebindChildren();
-                mCount = count;
-            }
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    private int getItemCount(int requestedHeight) {
-        int desiredCount = getDesiredCount();
-        return mEnableAutoSizing ? Math.min(requestedHeight / mItemSize, desiredCount)
-                : desiredCount;
-    }
-
-    private int getDesiredCount() {
-        return mAdapter != null ? mAdapter.getCount() : 0;
-    }
-
-    private void postRebindChildren() {
-        mHandler.post(mBindChildren);
-    }
-
-    private void rebindChildren() {
-        if (mAdapter == null) {
-            return;
-        }
-        for (int i = 0; i < mCount; i++) {
-            View v = i < getChildCount() ? getChildAt(i) : null;
-            View newView = mAdapter.getView(i, v, this);
-            if (newView != v) {
-                if (v != null) {
-                    removeView(v);
-                }
-                addView(newView, i);
-            }
-        }
-        // Ditch extra views.
-        while (getChildCount() > mCount) {
-            removeViewAt(getChildCount() - 1);
-        }
-    }
-
-    private final Runnable mBindChildren = new Runnable() {
-        @Override
-        public void run() {
-            rebindChildren();
-        }
-    };
-
-    private final DataSetObserver mDataObserver = new DataSetObserver() {
-        @Override
-        public void onChanged() {
-            if (mCount > getDesiredCount()) {
-                mCount = getDesiredCount();
-            }
-            postRebindChildren();
-        }
-
-        @Override
-        public void onInvalidated() {
-            postRebindChildren();
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 77feb90..5df8b80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -191,6 +191,10 @@
             reformatForNewFooter(securityFooter)
             val fgsFooter = fgsManagerFooterController.view
             securityFootersContainer?.addView(fgsFooter)
+            (fgsFooter.layoutParams as LinearLayout.LayoutParams).apply {
+                width = 0
+                weight = 1f
+            }
 
             val visibilityListener =
                 VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 707313f..f868055 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -44,7 +44,6 @@
     private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
     private  final Path mFancyClippingPath = new Path();
     private int mHeightOverride = -1;
-    private View mQSDetail;
     private QuickStatusBarHeader mHeader;
     private float mQsExpansion;
     private QSCustomizer mQSCustomizer;
@@ -63,7 +62,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
-        mQSDetail = findViewById(R.id.qs_detail);
         mHeader = findViewById(R.id.header);
         mQSCustomizer = findViewById(R.id.qs_customize);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -175,9 +173,6 @@
         int height = calculateContainerHeight();
         int scrollBottom = calculateContainerBottom();
         setBottom(getTop() + height);
-        mQSDetail.setBottom(getTop() + scrollBottom);
-        int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin;
-        mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin);
     }
 
     protected int calculateContainerHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
deleted file mode 100644
index 04e2252..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.drawable.Animatable;
-import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.plugins.qs.QSContainerController;
-import com.android.systemui.statusbar.CommandQueue;
-
-public class QSDetail extends LinearLayout {
-
-    private static final String TAG = "QSDetail";
-    private static final long FADE_DURATION = 300;
-
-    private final SparseArray<View> mDetailViews = new SparseArray<>();
-    private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
-
-    private ViewGroup mDetailContent;
-    private FalsingManager mFalsingManager;
-    protected TextView mDetailSettingsButton;
-    protected TextView mDetailDoneButton;
-    @VisibleForTesting
-    QSDetailClipper mClipper;
-    @Nullable
-    private DetailAdapter mDetailAdapter;
-    private QSPanelController mQsPanelController;
-
-    protected View mQsDetailHeader;
-    protected TextView mQsDetailHeaderTitle;
-    private ViewStub mQsDetailHeaderSwitchStub;
-    @Nullable
-    private Switch mQsDetailHeaderSwitch;
-    protected ImageView mQsDetailHeaderProgress;
-
-    @Nullable
-    protected QSTileHost mHost;
-
-    private boolean mScanState;
-    private boolean mClosingDetail;
-    private boolean mFullyExpanded;
-    private QuickStatusBarHeader mHeader;
-    private boolean mTriggeredExpand;
-    private boolean mShouldAnimate;
-    private int mOpenX;
-    private int mOpenY;
-    private boolean mAnimatingOpen;
-    private boolean mSwitchState;
-    private QSFooter mFooter;
-
-    @Nullable
-    private QSContainerController mQsContainerController;
-
-    public QSDetail(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        FontSizeUtils.updateFontSize(mDetailDoneButton, R.dimen.qs_detail_button_text_size);
-        FontSizeUtils.updateFontSize(mDetailSettingsButton, R.dimen.qs_detail_button_text_size);
-
-        for (int i = 0; i < mDetailViews.size(); i++) {
-            mDetailViews.valueAt(i).dispatchConfigurationChanged(newConfig);
-        }
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mDetailContent = findViewById(android.R.id.content);
-        mDetailSettingsButton = findViewById(android.R.id.button2);
-        mDetailDoneButton = findViewById(android.R.id.button1);
-
-        mQsDetailHeader = findViewById(R.id.qs_detail_header);
-        mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
-        mQsDetailHeaderSwitchStub = mQsDetailHeader.findViewById(R.id.toggle_stub);
-        mQsDetailHeaderProgress = findViewById(R.id.qs_detail_header_progress);
-
-        updateDetailText();
-
-        mClipper = new QSDetailClipper(this);
-    }
-
-    public void setContainerController(QSContainerController controller) {
-        mQsContainerController = controller;
-    }
-
-    /** */
-    public void setQsPanel(QSPanelController panelController, QuickStatusBarHeader header,
-            QSFooter footer, FalsingManager falsingManager) {
-        mQsPanelController = panelController;
-        mHeader = header;
-        mFooter = footer;
-        mHeader.setCallback(mQsPanelCallback);
-        mQsPanelController.setCallback(mQsPanelCallback);
-        mFalsingManager = falsingManager;
-    }
-
-    public void setHost(QSTileHost host) {
-        mHost = host;
-    }
-    public boolean isShowingDetail() {
-        return mDetailAdapter != null;
-    }
-
-    public void setFullyExpanded(boolean fullyExpanded) {
-        mFullyExpanded = fullyExpanded;
-    }
-
-    public void setExpanded(boolean qsExpanded) {
-        if (!qsExpanded) {
-            mTriggeredExpand = false;
-        }
-    }
-
-    private void updateDetailText() {
-        int resId = mDetailAdapter != null ? mDetailAdapter.getDoneText() : Resources.ID_NULL;
-        mDetailDoneButton.setText(
-                (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_done);
-        resId = mDetailAdapter != null ? mDetailAdapter.getSettingsText() : Resources.ID_NULL;
-        mDetailSettingsButton.setText(
-                (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_more_settings);
-    }
-
-    public void updateResources() {
-        updateDetailText();
-        MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
-        lp.topMargin = SystemBarUtils.getQuickQsOffsetHeight(mContext);
-        setLayoutParams(lp);
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        int bottomNavBar = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
-        MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
-        lp.bottomMargin = bottomNavBar;
-        setLayoutParams(lp);
-        return super.onApplyWindowInsets(insets);
-    }
-
-    public boolean isClosingDetail() {
-        return mClosingDetail;
-    }
-
-    public interface Callback {
-        /** Handle an event of showing detail. */
-        void onShowingDetail(@Nullable DetailAdapter detail, int x, int y);
-        void onToggleStateChanged(boolean state);
-        void onScanStateChanged(boolean state);
-    }
-
-    /** Handle an event of showing detail. */
-    public void handleShowingDetail(final @Nullable DetailAdapter adapter, int x, int y,
-            boolean toggleQs) {
-        final boolean showingDetail = adapter != null;
-        final boolean wasShowingDetail = mDetailAdapter != null;
-        setClickable(showingDetail);
-        if (showingDetail) {
-            setupDetailHeader(adapter);
-            if (toggleQs && !mFullyExpanded) {
-                mTriggeredExpand = true;
-                Dependency.get(CommandQueue.class).animateExpandSettingsPanel(null);
-            } else {
-                mTriggeredExpand = false;
-            }
-            mShouldAnimate = adapter.shouldAnimate();
-            mOpenX = x;
-            mOpenY = y;
-        } else {
-            // Ensure we collapse into the same point we opened from.
-            x = mOpenX;
-            y = mOpenY;
-            if (toggleQs && mTriggeredExpand) {
-                Dependency.get(CommandQueue.class).animateCollapsePanels();
-                mTriggeredExpand = false;
-            }
-            // Always animate on close, even if the last opened detail adapter had shouldAnimate()
-            // return false. This is necessary to avoid a race condition which could leave the
-            // keyguard in a bad state where QS remains visible underneath the notifications, clock,
-            // and status area.
-            mShouldAnimate = true;
-        }
-
-        boolean visibleDiff = wasShowingDetail != showingDetail;
-        if (!visibleDiff && !wasShowingDetail) return;  // already in right state
-        AnimatorListener listener;
-        if (showingDetail) {
-            int viewCacheIndex = adapter.getMetricsCategory();
-            View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex),
-                    mDetailContent);
-            if (detailView == null) throw new IllegalStateException("Must return detail view");
-
-            setupDetailFooter(adapter);
-
-            mDetailContent.removeAllViews();
-            mDetailContent.addView(detailView);
-            mDetailViews.put(viewCacheIndex, detailView);
-            Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
-            mUiEventLogger.log(adapter.openDetailEvent());
-            announceForAccessibility(mContext.getString(
-                    R.string.accessibility_quick_settings_detail,
-                    adapter.getTitle()));
-            mDetailAdapter = adapter;
-            listener = mHideGridContentWhenDone;
-            setVisibility(View.VISIBLE);
-            updateDetailText();
-        } else {
-            if (wasShowingDetail) {
-                Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
-                mUiEventLogger.log(mDetailAdapter.closeDetailEvent());
-            }
-            mClosingDetail = true;
-            mDetailAdapter = null;
-            listener = mTeardownDetailWhenDone;
-            // Only update visibility if already expanded. Otherwise, a race condition can cause the
-            // keyguard to enter a bad state where the QS tiles are displayed underneath the
-            // notifications, clock, and status area.
-            if (mQsPanelController.isExpanded()) {
-                mHeader.setVisibility(View.VISIBLE);
-                mFooter.setVisibility(View.VISIBLE);
-                mQsPanelController.setGridContentVisibility(true);
-                mQsPanelCallback.onScanStateChanged(false);
-            }
-        }
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        animateDetailVisibleDiff(x, y, visibleDiff, listener);
-        if (mQsContainerController != null) {
-            mQsContainerController.setDetailShowing(showingDetail);
-        }
-    }
-
-    protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) {
-        if (visibleDiff) {
-            mAnimatingOpen = mDetailAdapter != null;
-            if (mFullyExpanded || mDetailAdapter != null) {
-                setAlpha(1);
-                mClipper.updateCircularClip(mShouldAnimate, x, y, mDetailAdapter != null, listener);
-            } else {
-                animate().alpha(0)
-                        .setDuration(mShouldAnimate ? FADE_DURATION : 0)
-                        .setListener(listener)
-                        .start();
-            }
-        }
-    }
-
-    protected void setupDetailFooter(DetailAdapter adapter) {
-        final Intent settingsIntent = adapter.getSettingsIntent();
-        mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
-        mDetailSettingsButton.setOnClickListener(v -> {
-            if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                return;
-            }
-            Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
-                    adapter.getMetricsCategory());
-            mUiEventLogger.log(adapter.moreSettingsEvent());
-            Dependency.get(ActivityStarter.class)
-                    .postStartActivityDismissingKeyguard(settingsIntent, 0);
-        });
-        mDetailDoneButton.setOnClickListener(v -> {
-            if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                return;
-            }
-            announceForAccessibility(
-                    mContext.getString(R.string.accessibility_desc_quick_settings));
-            if (!adapter.onDoneButtonClicked()) {
-                mQsPanelController.closeDetail();
-            }
-        });
-    }
-
-    protected void setupDetailHeader(final DetailAdapter adapter) {
-        mQsDetailHeaderTitle.setText(adapter.getTitle());
-        final Boolean toggleState = adapter.getToggleState();
-        if (toggleState == null) {
-            if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
-            mQsDetailHeader.setClickable(false);
-        } else {
-            if (mQsDetailHeaderSwitch == null) {
-                mQsDetailHeaderSwitch = (Switch) mQsDetailHeaderSwitchStub.inflate();
-            }
-            mQsDetailHeaderSwitch.setVisibility(VISIBLE);
-            handleToggleStateChanged(toggleState, adapter.getToggleEnabled());
-            mQsDetailHeader.setClickable(true);
-            mQsDetailHeader.setOnClickListener(v -> {
-                if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                    return;
-                }
-                boolean checked = !mQsDetailHeaderSwitch.isChecked();
-                mQsDetailHeaderSwitch.setChecked(checked);
-                adapter.setToggleState(checked);
-            });
-        }
-    }
-
-    private void handleToggleStateChanged(boolean state, boolean toggleEnabled) {
-        mSwitchState = state;
-        if (mAnimatingOpen) {
-            return;
-        }
-        if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setChecked(state);
-        mQsDetailHeader.setEnabled(toggleEnabled);
-        if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setEnabled(toggleEnabled);
-    }
-
-    private void handleScanStateChanged(boolean state) {
-        if (mScanState == state) return;
-        mScanState = state;
-        final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable();
-        if (state) {
-            mQsDetailHeaderProgress.animate().cancel();
-            mQsDetailHeaderProgress.animate()
-                    .alpha(1)
-                    .withEndAction(anim::start)
-                    .start();
-        } else {
-            mQsDetailHeaderProgress.animate().cancel();
-            mQsDetailHeaderProgress.animate()
-                    .alpha(0f)
-                    .withEndAction(anim::stop)
-                    .start();
-        }
-    }
-
-    private void checkPendingAnimations() {
-        handleToggleStateChanged(mSwitchState,
-                            mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
-    }
-
-    protected Callback mQsPanelCallback = new Callback() {
-        @Override
-        public void onToggleStateChanged(final boolean state) {
-            post(new Runnable() {
-                @Override
-                public void run() {
-                    handleToggleStateChanged(state,
-                            mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
-                }
-            });
-        }
-
-        @Override
-        public void onShowingDetail(
-                final @Nullable DetailAdapter detail, final int x, final int y) {
-            post(new Runnable() {
-                @Override
-                public void run() {
-                    if (isAttachedToWindow()) {
-                        handleShowingDetail(detail, x, y, false /* toggleQs */);
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onScanStateChanged(final boolean state) {
-            post(new Runnable() {
-                @Override
-                public void run() {
-                    handleScanStateChanged(state);
-                }
-            });
-        }
-    };
-
-    private final AnimatorListenerAdapter mHideGridContentWhenDone = new AnimatorListenerAdapter() {
-        public void onAnimationCancel(Animator animation) {
-            // If we have been cancelled, remove the listener so that onAnimationEnd doesn't get
-            // called, this will avoid accidentally turning off the grid when we don't want to.
-            animation.removeListener(this);
-            mAnimatingOpen = false;
-            checkPendingAnimations();
-        };
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            // Only hide content if still in detail state.
-            if (mDetailAdapter != null) {
-                mQsPanelController.setGridContentVisibility(false);
-                mHeader.setVisibility(View.INVISIBLE);
-                mFooter.setVisibility(View.INVISIBLE);
-            }
-            mAnimatingOpen = false;
-            checkPendingAnimations();
-        }
-    };
-
-    private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
-        public void onAnimationEnd(Animator animation) {
-            mDetailContent.removeAllViews();
-            setVisibility(View.INVISIBLE);
-            mClosingDetail = false;
-        };
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
index 43136d3..b02efba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
@@ -25,7 +25,7 @@
 
 import androidx.annotation.Nullable;
 
-/** Helper for quick settings detail panel clip animations. **/
+/** Helper for quick settings detail panel clip animations. Currently used by the customizer **/
 public class QSDetailClipper {
 
     private final View mDetail;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
deleted file mode 100644
index afd4f0f..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.qs.DetailAdapter;
-
-import javax.inject.Inject;
-
-/**
- * Proxy class for talking with the QSPanel and showing custom content within it.
- */
-@SysUISingleton
-public class QSDetailDisplayer {
-    @Nullable
-    private QSPanelController mQsPanelController;
-
-    @Inject
-    public QSDetailDisplayer() {
-    }
-
-    public void setQsPanelController(@Nullable QSPanelController qsPanelController) {
-        mQsPanelController = qsPanelController;
-    }
-
-    /** Show the supplied DetailAdapter in the Quick Settings. */
-    public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
-        if (mQsPanelController != null) {
-            mQsPanelController.showDetailAdapter(detailAdapter, x, y);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
deleted file mode 100644
index eb3247b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSTile;
-
-/**
- * Quick settings common detail view with line items.
- */
-public class QSDetailItems extends FrameLayout {
-    private static final String TAG = "QSDetailItems";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private final int mQsDetailIconOverlaySize;
-    private final Context mContext;
-    private final H mHandler = new H();
-    private final Adapter mAdapter = new Adapter();
-
-    private String mTag;
-    @Nullable
-    private Callback mCallback;
-    private boolean mItemsVisible = true;
-    private AutoSizingList mItemList;
-    private View mEmpty;
-    private TextView mEmptyText;
-    private ImageView mEmptyIcon;
-
-    private Item[] mItems;
-
-    public QSDetailItems(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
-        mTag = TAG;
-        mQsDetailIconOverlaySize = (int) getResources().getDimension(
-                R.dimen.qs_detail_icon_overlay_size);
-    }
-
-    public static QSDetailItems convertOrInflate(Context context, View convert, ViewGroup parent) {
-        if (convert instanceof QSDetailItems) {
-            return (QSDetailItems) convert;
-        }
-        return (QSDetailItems) LayoutInflater.from(context).inflate(R.layout.qs_detail_items,
-                parent, false);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mItemList = findViewById(android.R.id.list);
-        mItemList.setVisibility(GONE);
-        mItemList.setAdapter(mAdapter);
-        mEmpty = findViewById(android.R.id.empty);
-        mEmpty.setVisibility(GONE);
-        mEmptyText = mEmpty.findViewById(android.R.id.title);
-        mEmptyIcon = mEmpty.findViewById(android.R.id.icon);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        FontSizeUtils.updateFontSize(mEmptyText, R.dimen.qs_detail_empty_text_size);
-        int count = mItemList.getChildCount();
-        for (int i = 0; i < count; i++) {
-            View item = mItemList.getChildAt(i);
-            FontSizeUtils.updateFontSize(item, android.R.id.title,
-                    R.dimen.qs_detail_item_primary_text_size);
-            FontSizeUtils.updateFontSize(item, android.R.id.summary,
-                    R.dimen.qs_detail_item_secondary_text_size);
-        }
-    }
-
-    public void setTagSuffix(String suffix) {
-        mTag = TAG + "." + suffix;
-    }
-
-    public void setEmptyState(int icon, int text) {
-        mEmptyIcon.post(() -> {
-            mEmptyIcon.setImageResource(icon);
-            mEmptyText.setText(text);
-        });
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (DEBUG) Log.d(mTag, "onAttachedToWindow");
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
-        mCallback = null;
-    }
-
-    public void setCallback(Callback callback) {
-        mHandler.removeMessages(H.SET_CALLBACK);
-        mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
-    }
-
-    /** Set items. */
-    public void setItems(@Nullable Item[] items) {
-        mHandler.removeMessages(H.SET_ITEMS);
-        mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget();
-    }
-
-    public void setItemsVisible(boolean visible) {
-        mHandler.removeMessages(H.SET_ITEMS_VISIBLE);
-        mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
-    }
-
-    private void handleSetCallback(Callback callback) {
-        mCallback = callback;
-    }
-
-    private void handleSetItems(Item[] items) {
-        final int itemCount = items != null ? items.length : 0;
-        mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
-        mItemList.setVisibility(itemCount == 0 ? GONE : VISIBLE);
-        mItems = items;
-        mAdapter.notifyDataSetChanged();
-    }
-
-    private void handleSetItemsVisible(boolean visible) {
-        if (mItemsVisible == visible) return;
-        mItemsVisible = visible;
-        for (int i = 0; i < mItemList.getChildCount(); i++) {
-            mItemList.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
-        }
-    }
-
-    private class Adapter extends BaseAdapter {
-
-        @Override
-        public int getCount() {
-            return mItems != null ? mItems.length : 0;
-        }
-
-        @Override
-        public Object getItem(int position) {
-            return mItems[position];
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return 0;
-        }
-
-        @Override
-        public View getView(int position, View view, ViewGroup parent) {
-            final Item item = mItems[position];
-            if (view == null) {
-                view = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item, parent,
-                        false);
-            }
-            view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
-            final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
-            if (item.icon != null) {
-                iv.setImageDrawable(item.icon.getDrawable(iv.getContext()));
-            } else {
-                iv.setImageResource(item.iconResId);
-            }
-            iv.getOverlay().clear();
-            if (item.overlay != null) {
-                item.overlay.setBounds(0, 0, mQsDetailIconOverlaySize, mQsDetailIconOverlaySize);
-                iv.getOverlay().add(item.overlay);
-            }
-            final TextView title = (TextView) view.findViewById(android.R.id.title);
-            title.setText(item.line1);
-            final TextView summary = (TextView) view.findViewById(android.R.id.summary);
-            final boolean twoLines = !TextUtils.isEmpty(item.line2);
-            title.setMaxLines(twoLines ? 1 : 2);
-            summary.setVisibility(twoLines ? VISIBLE : GONE);
-            summary.setText(twoLines ? item.line2 : null);
-            view.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (mCallback != null) {
-                        mCallback.onDetailItemClick(item);
-                    }
-                }
-            });
-
-            final ImageView icon2 = (ImageView) view.findViewById(android.R.id.icon2);
-            if (item.canDisconnect) {
-                icon2.setImageResource(R.drawable.ic_qs_cancel);
-                icon2.setVisibility(VISIBLE);
-                icon2.setClickable(true);
-                icon2.setOnClickListener(new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        if (mCallback != null) {
-                            mCallback.onDetailItemDisconnect(item);
-                        }
-                    }
-                });
-            } else if (item.icon2 != -1) {
-                icon2.setVisibility(VISIBLE);
-                icon2.setImageResource(item.icon2);
-                icon2.setClickable(false);
-            } else {
-                icon2.setVisibility(GONE);
-            }
-
-            return view;
-        }
-    };
-
-    private class H extends Handler {
-        private static final int SET_ITEMS = 1;
-        private static final int SET_CALLBACK = 2;
-        private static final int SET_ITEMS_VISIBLE = 3;
-
-        public H() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == SET_ITEMS) {
-                handleSetItems((Item[]) msg.obj);
-            } else if (msg.what == SET_CALLBACK) {
-                handleSetCallback((QSDetailItems.Callback) msg.obj);
-            } else if (msg.what == SET_ITEMS_VISIBLE) {
-                handleSetItemsVisible(msg.arg1 != 0);
-            }
-        }
-    }
-
-    public static class Item {
-        public Item(int iconResId, CharSequence line1, Object tag) {
-            this.iconResId = iconResId;
-            this.line1 = line1;
-            this.tag = tag;
-        }
-
-        public int iconResId;
-        @Nullable
-        public QSTile.Icon icon;
-        @Nullable
-        public Drawable overlay;
-        public CharSequence line1;
-        @Nullable
-        public CharSequence line2;
-        public Object tag;
-        public boolean canDisconnect;
-        public int icon2 = -1;
-    }
-
-    public interface Callback {
-        void onDetailItemClick(Item item);
-        void onDetailItemDisconnect(Item item);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
index 26adf46..24bb16a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -124,13 +124,13 @@
     @UiEvent(doc = "The current user has been switched in the detail panel")
     QS_USER_SWITCH(424),
 
-    @UiEvent(doc = "User switcher QS detail panel open")
+    @UiEvent(doc = "User switcher QS dialog open")
     QS_USER_DETAIL_OPEN(425),
 
-    @UiEvent(doc = "User switcher QS detail panel closed")
+    @UiEvent(doc = "User switcher QS dialog closed")
     QS_USER_DETAIL_CLOSE(426),
 
-    @UiEvent(doc = "User switcher QS detail panel more settings pressed")
+    @UiEvent(doc = "User switcher QS dialog more settings pressed")
     QS_USER_MORE_SETTINGS(427),
 
     @UiEvent(doc = "The user has added a guest in the detail panel")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 50952bd..ea68c405 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -33,7 +33,6 @@
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
-import android.widget.FrameLayout.LayoutParams;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -85,7 +84,6 @@
     private QSSquishinessController mQSSquishinessController;
     protected QuickStatusBarHeader mHeader;
     protected NonInterceptingScrollView mQSPanelScrollView;
-    private QSDetail mQSDetail;
     private boolean mListening;
     private QSContainerImpl mContainer;
     private int mLayoutDirection;
@@ -96,8 +94,6 @@
     private boolean mQsDisabled;
 
     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
-    private final CommandQueue mCommandQueue;
-    private final QSDetailDisplayer mQsDetailDisplayer;
     private final MediaHost mQsMediaHost;
     private final MediaHost mQqsMediaHost;
     private final QSFragmentComponent.Factory mQsComponentFactory;
@@ -126,7 +122,6 @@
      * otherwise.
      */
     private boolean mInSplitShade;
-    private boolean mPulseExpanding;
 
     /**
      * Are we currently transitioning from lockscreen to the full shade?
@@ -150,15 +145,13 @@
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
             QSTileHost qsTileHost,
             StatusBarStateController statusBarStateController, CommandQueue commandQueue,
-            QSDetailDisplayer qsDetailDisplayer, @Named(QS_PANEL) MediaHost qsMediaHost,
+            @Named(QS_PANEL) MediaHost qsMediaHost,
             @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
             KeyguardBypassController keyguardBypassController,
             QSFragmentComponent.Factory qsComponentFactory,
             QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
             FalsingManager falsingManager, DumpManager dumpManager) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
-        mCommandQueue = commandQueue;
-        mQsDetailDisplayer = qsDetailDisplayer;
         mQsMediaHost = qsMediaHost;
         mQqsMediaHost = qqsMediaHost;
         mQsComponentFactory = qsComponentFactory;
@@ -209,19 +202,15 @@
                         mScrollListener.onQsPanelScrollChanged(scrollY);
                     }
         });
-        mQSDetail = view.findViewById(R.id.qs_detail);
         mHeader = view.findViewById(R.id.header);
         mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
         mFooter = qsFragmentComponent.getQSFooter();
 
-        mQsDetailDisplayer.setQsPanelController(mQSPanelController);
-
         mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
         mQSContainerImplController.init();
         mContainer = mQSContainerImplController.getView();
         mDumpManager.registerDumpable(mContainer.getClass().getName(), mContainer);
 
-        mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter, mFalsingManager);
         mQSAnimator = qsFragmentComponent.getQSAnimator();
         mQSSquishinessController = qsFragmentComponent.getQSSquishinessController();
 
@@ -237,7 +226,6 @@
                 mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState);
             }
         }
-        setHost(mHost);
         mStatusBarStateController.addCallback(this);
         onStateChanged(mStatusBarStateController.getState());
         view.addOnLayoutChangeListener(
@@ -271,7 +259,6 @@
             setListening(false);
         }
         mQSCustomizerController.setQs(null);
-        mQsDetailDisplayer.setQsPanelController(null);
         mScrollListener = null;
         mDumpManager.unregisterDumpable(mContainer.getClass().getName());
     }
@@ -352,7 +339,6 @@
     @Override
     public void setContainerController(QSContainerController controller) {
         mQSCustomizerController.setContainerController(controller);
-        mQSDetail.setContainerController(controller);
     }
 
     @Override
@@ -360,10 +346,6 @@
         return mQSCustomizerController.isCustomizing();
     }
 
-    public void setHost(QSTileHost qsh) {
-        mQSDetail.setHost(qsh);
-    }
-
     @Override
     public void disable(int displayId, int state1, int state2, boolean animate) {
         if (displayId != getContext().getDisplayId()) {
@@ -392,7 +374,6 @@
         final boolean expandVisually = expanded || mStackScrollerOverscrolling
                 || mHeaderAnimating;
         mQSPanelController.setExpanded(expanded);
-        mQSDetail.setExpanded(expanded);
         boolean keyguardShowing = isKeyguardState();
         mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating
                 || mShowCollapsedOnKeyguard)
@@ -444,7 +425,7 @@
 
     @Override
     public boolean isShowingDetail() {
-        return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail();
+        return mQSCustomizerController.isCustomizing();
     }
 
     @Override
@@ -573,7 +554,6 @@
         if (fullyCollapsed) {
             mQSPanelScrollView.setScrollY(0);
         }
-        mQSDetail.setFullyExpanded(fullyExpanded);
 
         if (!fullyExpanded) {
             // Set bounds on the QS panel so it doesn't run over the header when animating.
@@ -732,14 +712,7 @@
         if (mQSCustomizerController.isCustomizing()) {
             return getView().getHeight();
         }
-        if (mQSDetail.isClosingDetail()) {
-            LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams();
-            int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
-                    + mQSPanelScrollView.getMeasuredHeight();
-            return panelHeight + getView().getPaddingBottom();
-        } else {
-            return getView().getMeasuredHeight();
-        }
+        return getView().getMeasuredHeight();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 7c04cd4..0c854df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -41,10 +41,8 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.RemeasuringLinearLayout;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -80,7 +78,6 @@
     protected boolean mExpanded;
     protected boolean mListening;
 
-    private QSDetail.Callback mCallback;
     protected QSTileHost mHost;
     private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
             new ArrayList<>();
@@ -100,9 +97,6 @@
     private int mContentMarginEnd;
     private boolean mUsingHorizontalLayout;
 
-    private Record mDetailRecord;
-
-    private BrightnessMirrorController mBrightnessMirrorController;
     private LinearLayout mHorizontalLinearLayout;
     protected LinearLayout mHorizontalContentContainer;
 
@@ -315,24 +309,12 @@
         view.setVisibility(TunerService.parseIntegerSwitch(newValue, true) ? VISIBLE : GONE);
     }
 
-    /** */
-    public void openDetails(QSTile tile) {
-        // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
-        // QSFactory will not be able to create a tile and getTile will return null
-        if (tile != null) {
-            showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0});
-        }
-    }
 
     @Nullable
     View getBrightnessView() {
         return mBrightnessView;
     }
 
-    public void setCallback(QSDetail.Callback callback) {
-        mCallback = callback;
-    }
-
     /**
      * Links the footer's page indicator, which is used in landscape orientation to save space.
      *
@@ -519,25 +501,6 @@
         mListening = listening;
     }
 
-    public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
-        int xInWindow = locationInWindow[0];
-        int yInWindow = locationInWindow[1];
-        ((View) getParent()).getLocationInWindow(locationInWindow);
-
-        Record r = new Record();
-        r.detailAdapter = adapter;
-        r.x = xInWindow - locationInWindow[0];
-        r.y = yInWindow - locationInWindow[1];
-
-        locationInWindow[0] = xInWindow;
-        locationInWindow[1] = yInWindow;
-
-        showDetail(show, r);
-    }
-
-    protected void showDetail(boolean show, Record r) {
-        mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
-    }
 
     protected void drawTile(QSPanelControllerBase.TileRecord r, QSTile.State state) {
         r.tileView.onStateChanged(state);
@@ -565,30 +528,6 @@
             public void onStateChanged(QSTile.State state) {
                 drawTile(tileRecord, state);
             }
-
-            @Override
-            public void onShowDetail(boolean show) {
-                // Both the collapsed and full QS panels get this callback, this check determines
-                // which one should handle showing the detail.
-                if (shouldShowDetail()) {
-                    QSPanel.this.showDetail(show, tileRecord);
-                }
-            }
-
-            @Override
-            public void onToggleStateChanged(boolean state) {
-                if (mDetailRecord == tileRecord) {
-                    fireToggleStateChanged(state);
-                }
-            }
-
-            @Override
-            public void onScanStateChanged(boolean state) {
-                tileRecord.scanState = state;
-                if (mDetailRecord == tileRecord) {
-                    fireScanStateChanged(tileRecord.scanState);
-                }
-            }
         };
 
         tileRecord.tile.addCallback(callback);
@@ -605,72 +544,10 @@
         mTileLayout.removeTile(tileRecord);
     }
 
-    void closeDetail() {
-        showDetail(false, mDetailRecord);
-    }
-
     public int getGridHeight() {
         return getMeasuredHeight();
     }
 
-    protected void handleShowDetail(Record r, boolean show) {
-        if (r instanceof QSPanelControllerBase.TileRecord) {
-            handleShowDetailTile((QSPanelControllerBase.TileRecord) r, show);
-        } else {
-            int x = 0;
-            int y = 0;
-            if (r != null) {
-                x = r.x;
-                y = r.y;
-            }
-            handleShowDetailImpl(r, show, x, y);
-        }
-    }
-
-    private void handleShowDetailTile(QSPanelControllerBase.TileRecord r, boolean show) {
-        if ((mDetailRecord != null) == show && mDetailRecord == r) return;
-
-        if (show) {
-            r.detailAdapter = r.tile.getDetailAdapter();
-            if (r.detailAdapter == null) return;
-        }
-        r.tile.setDetailListening(show);
-        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
-        int y = r.tileView.getDetailY() + mTileLayout.getOffsetTop(r) + getTop();
-        handleShowDetailImpl(r, show, x, y);
-    }
-
-    private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
-        setDetailRecord(show ? r : null);
-        fireShowingDetail(show ? r.detailAdapter : null, x, y);
-    }
-
-    protected void setDetailRecord(Record r) {
-        if (r == mDetailRecord) return;
-        mDetailRecord = r;
-        final boolean scanState = mDetailRecord instanceof QSPanelControllerBase.TileRecord
-                && ((QSPanelControllerBase.TileRecord) mDetailRecord).scanState;
-        fireScanStateChanged(scanState);
-    }
-
-    private void fireShowingDetail(DetailAdapter detail, int x, int y) {
-        if (mCallback != null) {
-            mCallback.onShowingDetail(detail, x, y);
-        }
-    }
-
-    private void fireToggleStateChanged(boolean state) {
-        if (mCallback != null) {
-            mCallback.onToggleStateChanged(state);
-        }
-    }
-
-    private void fireScanStateChanged(boolean state) {
-        if (mCallback != null) {
-            mCallback.onScanStateChanged(state);
-        }
-    }
-
     QSTileLayout getTileLayout() {
         return mTileLayout;
     }
@@ -774,26 +651,16 @@
     }
 
     private class H extends Handler {
-        private static final int SHOW_DETAIL = 1;
-        private static final int SET_TILE_VISIBILITY = 2;
-        private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3;
+        private static final int ANNOUNCE_FOR_ACCESSIBILITY = 1;
 
         @Override
         public void handleMessage(Message msg) {
-            if (msg.what == SHOW_DETAIL) {
-                handleShowDetail((Record) msg.obj, msg.arg1 != 0);
-            } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
+            if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
                 announceForAccessibility((CharSequence) msg.obj);
             }
         }
     }
 
-    protected static class Record {
-        DetailAdapter detailAdapter;
-        int x;
-        int y;
-    }
-
     public interface QSTileLayout {
         /** */
         default void saveInstanceState(Bundle outState) {}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 418c4ae..6a7f3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -36,15 +36,12 @@
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.Utils;
@@ -57,7 +54,6 @@
  */
 @QSScope
 public class QSPanelController extends QSPanelControllerBase<QSPanel> {
-    public static final String QS_REMOVE_LABELS = "sysui_remove_labels";
 
     private final QSFgsManagerFooter mQSFgsManagerFooter;
     private final QSSecurityFooter mQsSecurityFooter;
@@ -65,7 +61,6 @@
     private final QSCustomizerController mQsCustomizerController;
     private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
     private final FalsingManager mFalsingManager;
-    private final CommandQueue mCommandQueue;
     private final BrightnessController mBrightnessController;
     private final BrightnessSliderController mBrightnessSliderController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
@@ -107,7 +102,7 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSliderController.Factory brightnessSliderFactory,
-            FalsingManager falsingManager, CommandQueue commandQueue, FeatureFlags featureFlags) {
+            FalsingManager falsingManager, FeatureFlags featureFlags) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mQSFgsManagerFooter = qsFgsManagerFooter;
@@ -116,7 +111,6 @@
         mQsCustomizerController = qsCustomizerController;
         mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
         mFalsingManager = falsingManager;
-        mCommandQueue = commandQueue;
 
         mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
         mView.setBrightnessView(mBrightnessSliderController.getRootView());
@@ -221,15 +215,6 @@
         return mHost;
     }
 
-
-    /** Open the details for a specific tile.. */
-    public void openDetails(String subPanel) {
-        QSTile tile = getTile(subPanel);
-        if (tile != null) {
-            mView.openDetails(tile);
-        }
-    }
-
     /** Show the device monitoring dialog. */
     public void showDeviceMonitoringDialog() {
         mQsSecurityFooter.showDeviceMonitoringDialog();
@@ -261,11 +246,6 @@
     }
 
     /** */
-    public void setCallback(QSDetail.Callback qsPanelCallback) {
-        mView.setCallback(qsPanelCallback);
-    }
-
-    /** */
     public void setGridContentVisibility(boolean visible) {
         int newVis = visible ? View.VISIBLE : View.INVISIBLE;
         setVisibility(newVis);
@@ -294,19 +274,6 @@
     }
 
     /** */
-    public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
-        // TODO(b/199296365)
-        // Workaround for opening detail from QQS, when there might not be enough space to
-        // display e.g. in case of multiuser detail from split shade. Currently showing detail works
-        // only for QS (mView below) and that's why expanding panel (thus showing QS instead of QQS)
-        // makes it displayed correctly.
-        if (!isExpanded()) {
-            mCommandQueue.animateExpandSettingsPanel(null);
-        }
-        mView.showDetailAdapter(true, detailAdapter, new int[]{x, y});
-    }
-
-    /** */
     public void setFooterPageIndicator(PageIndicator pageIndicator) {
         mView.setFooterPageIndicator(pageIndicator);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index d4d6da8..0bff722 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -298,21 +298,8 @@
             mQsCustomizerController.hide();
             return;
         }
-        mView.closeDetail();
     }
 
-    /** */
-    public void openDetails(String subPanel) {
-        QSTile tile = getTile(subPanel);
-        // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
-        // QSFactory will not be able to create a tile and getTile will return null
-        if (tile != null) {
-            mView.showDetailAdapter(
-                    true, tile.getDetailAdapter(), new int[]{mView.getWidth() / 2, 0});
-        }
-    }
-
-
     void setListening(boolean listening) {
         mView.setListening(listening);
 
@@ -429,7 +416,7 @@
     }
 
     /** */
-    public static final class TileRecord extends QSPanel.Record {
+    public static final class TileRecord {
         public TileRecord(QSTile tile, com.android.systemui.plugins.qs.QSTileView tileView) {
             this.tile = tile;
             this.tileView = tileView;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index d3f8db38..8c08873 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -39,7 +39,6 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
-import com.android.systemui.qs.QSDetail.Callback;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.phone.StatusIconContainer;
@@ -548,10 +547,6 @@
         post(() -> setClickable(!mExpanded));
     }
 
-    public void setCallback(Callback qsPanelCallback) {
-        mHeaderQsPanel.setCallback(qsPanelCallback);
-    }
-
     private void setContentMargins(View view, int marginStart, int marginEnd) {
         MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
         lp.setMarginStart(marginStart);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index b29687f..32a7916 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -205,15 +205,6 @@
                 finished();
             }
         }
-
-        @Override
-        public void onShowDetail(boolean show) {}
-
-        @Override
-        public void onToggleStateChanged(boolean state) {}
-
-        @Override
-        public void onScanStateChanged(boolean state) {}
     }
 
     private void addPackageTiles(final QSTileHost host) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 73d6b97..237b66e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -21,6 +21,7 @@
 import android.content.ComponentName
 import android.content.DialogInterface
 import android.graphics.drawable.Icon
+import android.os.RemoteException
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.internal.statusbar.IAddTileResultCallback
@@ -32,6 +33,7 @@
 import com.android.systemui.R
 import com.android.systemui.statusbar.CommandQueue
 import java.io.PrintWriter
+import java.util.concurrent.atomic.AtomicBoolean
 import java.util.function.Consumer
 import javax.inject.Inject
 
@@ -67,7 +69,11 @@
             callback: IAddTileResultCallback
         ) {
             requestTileAdd(componentName, appName, label, icon) {
-                callback.onTileRequest(it)
+                try {
+                    callback.onTileRequest(it)
+                } catch (e: RemoteException) {
+                    Log.e(TAG, "Couldn't respond to request", e)
+                }
             }
         }
 
@@ -105,7 +111,7 @@
             eventLogger.logTileAlreadyAdded(packageName, instanceId)
             return
         }
-        val dialogResponse = Consumer<Int> { response ->
+        val dialogResponse = SingleShotConsumer<Int> { response ->
             if (response == ADD_TILE) {
                 addTile(componentName)
             }
@@ -127,7 +133,7 @@
 
     private fun createDialog(
         tileData: TileRequestDialog.TileData,
-        responseHandler: Consumer<Int>
+        responseHandler: SingleShotConsumer<Int>
     ): SystemUIDialog {
         val dialogClickListener = DialogInterface.OnClickListener { _, which ->
             if (which == Dialog.BUTTON_POSITIVE) {
@@ -141,6 +147,10 @@
             setShowForAllUsers(true)
             setCanceledOnTouchOutside(true)
             setOnCancelListener { responseHandler.accept(DISMISSED) }
+            // We want this in case the dialog is dismissed without it being cancelled (for example
+            // by going home or locking the device). We use a SingleShotConsumer so the response
+            // is only sent once, with the first value.
+            setOnDismissListener { responseHandler.accept(DISMISSED) }
             setPositiveButton(R.string.qs_tile_request_dialog_add, dialogClickListener)
             setNegativeButton(R.string.qs_tile_request_dialog_not_add, dialogClickListener)
         }
@@ -169,6 +179,16 @@
         }
     }
 
+    private class SingleShotConsumer<T>(private val consumer: Consumer<T>) : Consumer<T> {
+        private val dispatched = AtomicBoolean(false)
+
+        override fun accept(t: T) {
+            if (dispatched.compareAndSet(false, true)) {
+                consumer.accept(t)
+            }
+        }
+    }
+
     @SysUISingleton
     class Builder @Inject constructor(
         private val commandQueue: CommandQueue,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 86fc4de..1488231 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -53,7 +53,6 @@
 import com.android.systemui.qs.tiles.RotationLockTile;
 import com.android.systemui.qs.tiles.ScreenRecordTile;
 import com.android.systemui.qs.tiles.UiModeNightTile;
-import com.android.systemui.qs.tiles.UserTile;
 import com.android.systemui.qs.tiles.WifiTile;
 import com.android.systemui.qs.tiles.WorkModeTile;
 import com.android.systemui.util.leak.GarbageMonitor;
@@ -82,7 +81,6 @@
     private final Provider<LocationTile> mLocationTileProvider;
     private final Provider<CastTile> mCastTileProvider;
     private final Provider<HotspotTile> mHotspotTileProvider;
-    private final Provider<UserTile> mUserTileProvider;
     private final Provider<BatterySaverTile> mBatterySaverTileProvider;
     private final Provider<DataSaverTile> mDataSaverTileProvider;
     private final Provider<NightDisplayTile> mNightDisplayTileProvider;
@@ -119,7 +117,6 @@
             Provider<LocationTile> locationTileProvider,
             Provider<CastTile> castTileProvider,
             Provider<HotspotTile> hotspotTileProvider,
-            Provider<UserTile> userTileProvider,
             Provider<BatterySaverTile> batterySaverTileProvider,
             Provider<DataSaverTile> dataSaverTileProvider,
             Provider<NightDisplayTile> nightDisplayTileProvider,
@@ -152,7 +149,6 @@
         mLocationTileProvider = locationTileProvider;
         mCastTileProvider = castTileProvider;
         mHotspotTileProvider = hotspotTileProvider;
-        mUserTileProvider = userTileProvider;
         mBatterySaverTileProvider = batterySaverTileProvider;
         mDataSaverTileProvider = dataSaverTileProvider;
         mNightDisplayTileProvider = nightDisplayTileProvider;
@@ -212,8 +208,6 @@
                 return mCastTileProvider.get();
             case "hotspot":
                 return mHotspotTileProvider.get();
-            case "user":
-                return mUserTileProvider.get();
             case "battery":
                 return mBatterySaverTileProvider.get();
             case "saver":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 6d9d5b1..131589f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -60,7 +60,6 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
@@ -261,16 +260,6 @@
         return new QSIconViewImpl(context);
     }
 
-    /** Returns corresponding DetailAdapter. */
-    @Nullable
-    public DetailAdapter getDetailAdapter() {
-        return null; // optional
-    }
-
-    protected DetailAdapter createDetailAdapter() {
-        throw new UnsupportedOperationException();
-    }
-
     /**
      * Is a startup check whether this device currently supports this tile.
      * Should not be used to conditionally hide tiles.  Only checked on tile
@@ -337,10 +326,6 @@
                 .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
     }
 
-    public void showDetail(boolean show) {
-        mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
-    }
-
     public void refreshState() {
         refreshState(null);
     }
@@ -353,14 +338,6 @@
         mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
     }
 
-    public void fireToggleStateChanged(boolean state) {
-        mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
-    }
-
-    public void fireScanStateChanged(boolean state) {
-        mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
-    }
-
     public void destroy() {
         mHandler.sendEmptyMessage(H.DESTROY);
     }
@@ -462,29 +439,6 @@
         }
     }
 
-    private void handleShowDetail(boolean show) {
-        mShowingDetail = show;
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            mCallbacks.get(i).onShowDetail(show);
-        }
-    }
-
-    protected boolean isShowingDetail() {
-        return mShowingDetail;
-    }
-
-    private void handleToggleStateChanged(boolean state) {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            mCallbacks.get(i).onToggleStateChanged(state);
-        }
-    }
-
-    private void handleScanStateChanged(boolean state) {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            mCallbacks.get(i).onScanStateChanged(state);
-        }
-    }
-
     protected void handleUserSwitch(int newUserId) {
         handleRefreshState(null);
     }
@@ -591,17 +545,14 @@
         private static final int SECONDARY_CLICK = 3;
         private static final int LONG_CLICK = 4;
         private static final int REFRESH_STATE = 5;
-        private static final int SHOW_DETAIL = 6;
-        private static final int USER_SWITCH = 7;
-        private static final int TOGGLE_STATE_CHANGED = 8;
-        private static final int SCAN_STATE_CHANGED = 9;
-        private static final int DESTROY = 10;
-        private static final int REMOVE_CALLBACKS = 11;
-        private static final int REMOVE_CALLBACK = 12;
-        private static final int SET_LISTENING = 13;
+        private static final int USER_SWITCH = 6;
+        private static final int DESTROY = 7;
+        private static final int REMOVE_CALLBACKS = 8;
+        private static final int REMOVE_CALLBACK = 9;
+        private static final int SET_LISTENING = 10;
         @VisibleForTesting
-        protected static final int STALE = 14;
-        private static final int INITIALIZE = 15;
+        protected static final int STALE = 11;
+        private static final int INITIALIZE = 12;
 
         @VisibleForTesting
         protected H(Looper looper) {
@@ -639,18 +590,9 @@
                 } else if (msg.what == REFRESH_STATE) {
                     name = "handleRefreshState";
                     handleRefreshState(msg.obj);
-                } else if (msg.what == SHOW_DETAIL) {
-                    name = "handleShowDetail";
-                    handleShowDetail(msg.arg1 != 0);
                 } else if (msg.what == USER_SWITCH) {
                     name = "handleUserSwitch";
                     handleUserSwitch(msg.arg1);
-                } else if (msg.what == TOGGLE_STATE_CHANGED) {
-                    name = "handleToggleStateChanged";
-                    handleToggleStateChanged(msg.arg1 != 0);
-                } else if (msg.what == SCAN_STATE_CHANGED) {
-                    name = "handleScanStateChanged";
-                    handleScanStateChanged(msg.arg1 != 0);
                 } else if (msg.what == DESTROY) {
                     name = "handleDestroy";
                     handleDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 754f8e2..c61c18a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -19,7 +19,6 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -29,7 +28,6 @@
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.Switch;
 
 import androidx.annotation.Nullable;
@@ -38,24 +36,18 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSDetailItems;
-import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -65,7 +57,6 @@
     private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
 
     private final BluetoothController mController;
-    private final BluetoothDetailAdapter mDetailAdapter;
 
     @Inject
     public BluetoothTile(
@@ -82,16 +73,10 @@
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = bluetoothController;
-        mDetailAdapter = (BluetoothDetailAdapter) createDetailAdapter();
         mController.observe(getLifecycle(), mCallback);
     }
 
     @Override
-    public DetailAdapter getDetailAdapter() {
-        return mDetailAdapter;
-    }
-
-    @Override
     public BooleanState newTileState() {
         return new BooleanState();
     }
@@ -117,7 +102,6 @@
                     new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0);
             return;
         }
-        showDetail(true);
         if (!mState.value) {
             mController.setBluetoothEnabled(true);
         }
@@ -251,53 +235,14 @@
         @Override
         public void onBluetoothStateChange(boolean enabled) {
             refreshState();
-            if (isShowingDetail()) {
-                mDetailAdapter.updateItems();
-                fireToggleStateChanged(mDetailAdapter.getToggleState());
-            }
         }
 
         @Override
         public void onBluetoothDevicesChanged() {
             refreshState();
-            if (isShowingDetail()) {
-                mDetailAdapter.updateItems();
-            }
         }
     };
 
-    @Override
-    protected DetailAdapter createDetailAdapter() {
-        return new BluetoothDetailAdapter();
-    }
-
-    /**
-     * Bluetooth icon wrapper for Quick Settings with a battery indicator that reflects the
-     * connected device's battery level. This is used instead of
-     * {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to use a context
-     * that reflects dark/light theme attributes.
-     */
-    private class BluetoothBatteryTileIcon extends Icon {
-        private int mBatteryLevel;
-        private float mIconScale;
-
-        BluetoothBatteryTileIcon(int batteryLevel, float iconScale) {
-            mBatteryLevel = batteryLevel;
-            mIconScale = iconScale;
-        }
-
-        @Override
-        public Drawable getDrawable(Context context) {
-            // This method returns Pair<Drawable, String> while first value is the drawable
-            return BluetoothDeviceLayerDrawable.createLayerDrawable(
-                    context,
-                    R.drawable.ic_bluetooth_connected,
-                    mBatteryLevel,
-                    mIconScale);
-        }
-    }
-
-
     /**
      * Bluetooth icon wrapper (when connected with no battery indicator) for Quick Settings. This is
      * used instead of {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to
@@ -315,129 +260,4 @@
             return context.getDrawable(R.drawable.ic_bluetooth_connected);
         }
     }
-
-    protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
-        // We probably won't ever have space in the UI for more than 20 devices, so don't
-        // get info for them.
-        private static final int MAX_DEVICES = 20;
-        @Nullable
-        private QSDetailItems mItems;
-
-        @Override
-        public CharSequence getTitle() {
-            return mContext.getString(R.string.quick_settings_bluetooth_label);
-        }
-
-        @Override
-        public Boolean getToggleState() {
-            return mState.value;
-        }
-
-        @Override
-        public boolean getToggleEnabled() {
-            return mController.getBluetoothState() == BluetoothAdapter.STATE_OFF
-                    || mController.getBluetoothState() == BluetoothAdapter.STATE_ON;
-        }
-
-        @Override
-        public Intent getSettingsIntent() {
-            return BLUETOOTH_SETTINGS;
-        }
-
-        @Override
-        public void setToggleState(boolean state) {
-            MetricsLogger.action(mContext, MetricsEvent.QS_BLUETOOTH_TOGGLE, state);
-            mController.setBluetoothEnabled(state);
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return MetricsEvent.QS_BLUETOOTH_DETAILS;
-        }
-
-        @Override
-        public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
-            mItems.setTagSuffix("Bluetooth");
-            mItems.setCallback(this);
-            updateItems();
-            setItemsVisible(mState.value);
-            return mItems;
-        }
-
-        public void setItemsVisible(boolean visible) {
-            if (mItems == null) return;
-            mItems.setItemsVisible(visible);
-        }
-
-        private void updateItems() {
-            if (mItems == null) return;
-            if (mController.isBluetoothEnabled()) {
-                mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
-                        R.string.quick_settings_bluetooth_detail_empty_text);
-            } else {
-                mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
-                        R.string.bt_is_off);
-            }
-            ArrayList<Item> items = new ArrayList<Item>();
-            final Collection<CachedBluetoothDevice> devices = mController.getDevices();
-            if (devices != null) {
-                int connectedDevices = 0;
-                int count = 0;
-                for (CachedBluetoothDevice device : devices) {
-                    if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
-                    final Item item =
-                            new Item(
-                                    com.android.internal.R.drawable.ic_qs_bluetooth,
-                                    device.getName(),
-                                    device);
-                    int state = device.getMaxConnectionState();
-                    if (state == BluetoothProfile.STATE_CONNECTED) {
-                        item.iconResId = R.drawable.ic_bluetooth_connected;
-                        int batteryLevel = device.getBatteryLevel();
-                        if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                            item.icon = new BluetoothBatteryTileIcon(batteryLevel,1 /* iconScale */);
-                            item.line2 = mContext.getString(
-                                    R.string.quick_settings_connected_battery_level,
-                                    Utils.formatPercentage(batteryLevel));
-                        } else {
-                            item.line2 = mContext.getString(R.string.quick_settings_connected);
-                        }
-                        item.canDisconnect = true;
-                        items.add(connectedDevices, item);
-                        connectedDevices++;
-                    } else if (state == BluetoothProfile.STATE_CONNECTING) {
-                        item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
-                        item.line2 = mContext.getString(R.string.quick_settings_connecting);
-                        items.add(connectedDevices, item);
-                    } else {
-                        items.add(item);
-                    }
-                    if (++count == MAX_DEVICES) {
-                        break;
-                    }
-                }
-            }
-            mItems.setItems(items.toArray(new Item[items.size()]));
-        }
-
-        @Override
-        public void onDetailItemClick(Item item) {
-            if (item == null || item.tag == null) return;
-            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
-            if (device != null && device.getMaxConnectionState()
-                    == BluetoothProfile.STATE_DISCONNECTED) {
-                mController.connect(device);
-            }
-        }
-
-        @Override
-        public void onDetailItemDisconnect(Item item) {
-            if (item == null || item.tag == null) return;
-            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
-            if (device != null) {
-                mController.disconnect(device);
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 76c84f9..4d9c4c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.app.Dialog;
-import android.content.Context;
 import android.content.Intent;
 import android.media.MediaRouter.RouteInfo;
 import android.os.Handler;
@@ -29,8 +28,6 @@
 import android.service.quicksettings.Tile;
 import android.util.Log;
 import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.ViewGroup;
 import android.widget.Button;
 
 import androidx.annotation.Nullable;
@@ -44,11 +41,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSDetailItems;
-import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -62,7 +56,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -73,7 +66,6 @@
             new Intent(Settings.ACTION_CAST_SETTINGS);
 
     private final CastController mController;
-    private final CastDetailAdapter mDetailAdapter;
     private final KeyguardStateController mKeyguard;
     private final NetworkController mNetworkController;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -100,7 +92,6 @@
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = castController;
-        mDetailAdapter = new CastDetailAdapter();
         mKeyguard = keyguardStateController;
         mNetworkController = networkController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
@@ -111,11 +102,6 @@
     }
 
     @Override
-    public DetailAdapter getDetailAdapter() {
-        return mDetailAdapter;
-    }
-
-    @Override
     public BooleanState newTileState() {
         BooleanState state = new BooleanState();
         state.handlesLongClick = false;
@@ -154,14 +140,14 @@
         }
 
         List<CastDevice> activeDevices = getActiveDevices();
-        if (willPopDetail()) {
+        if (willPopDialog()) {
             if (!mKeyguard.isShowing()) {
-                showDetail(view);
+                showDialog(view);
             } else {
                 mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
                     // Dismissing the keyguard will collapse the shade, so we don't animate from the
                     // view here as it would not look good.
-                    showDetail(null /* view */);
+                    showDialog(null /* view */);
                 });
             }
         } else {
@@ -173,7 +159,7 @@
     // (neither routes nor projection), or if we have an active route. In other cases, we assume
     // that a projection is active. This is messy, but this tile never correctly handled the
     // case where multiple devices were active :-/.
-    private boolean willPopDetail() {
+    private boolean willPopDialog() {
         List<CastDevice> activeDevices = getActiveDevices();
         return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo);
     }
@@ -190,11 +176,6 @@
         return activeDevices;
     }
 
-    @Override
-    public void showDetail(boolean show) {
-        showDetail(null /* view */);
-    }
-
     private static class DialogHolder {
         private Dialog mDialog;
 
@@ -203,7 +184,7 @@
         }
     }
 
-    private void showDetail(@Nullable View view) {
+    private void showDialog(@Nullable View view) {
         mUiHandler.post(() -> {
             final DialogHolder holder = new DialogHolder();
             final Dialog dialog = MediaRouteDialogPresenter.createDialog(
@@ -268,10 +249,8 @@
             if (!state.value) {
                 state.secondaryLabel = "";
             }
-            state.contentDescription = state.contentDescription + ","
-                    + mContext.getString(R.string.accessibility_quick_settings_open_details);
             state.expandedAccessibilityClassName = Button.class.getName();
-            state.forceExpandIcon = willPopDetail();
+            state.forceExpandIcon = willPopDialog();
         } else {
             state.state = Tile.STATE_UNAVAILABLE;
             String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
@@ -279,7 +258,6 @@
             state.forceExpandIcon = false;
         }
         state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
-        mDetailAdapter.updateItems(devices);
     }
 
     @Override
@@ -339,118 +317,4 @@
             refreshState();
         }
     };
-
-    private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
-        private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>();
-
-        private QSDetailItems mItems;
-
-        @Override
-        public CharSequence getTitle() {
-            return mContext.getString(R.string.quick_settings_cast_title);
-        }
-
-        @Override
-        public Boolean getToggleState() {
-            return null;
-        }
-
-        @Override
-        public Intent getSettingsIntent() {
-            return CAST_SETTINGS;
-        }
-
-        @Override
-        public void setToggleState(boolean state) {
-            // noop
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return MetricsEvent.QS_CAST_DETAILS;
-        }
-
-        @Override
-        public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
-            mItems.setTagSuffix("Cast");
-            if (convertView == null) {
-                if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener");
-                mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
-                    @Override
-                    public void onViewAttachedToWindow(View v) {
-                        if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
-                    }
-
-                    @Override
-                    public void onViewDetachedFromWindow(View v) {
-                        if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
-                        mVisibleOrder.clear();
-                    }
-                });
-            }
-            mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty,
-                    R.string.quick_settings_cast_detail_empty_text);
-            mItems.setCallback(this);
-            updateItems(mController.getCastDevices());
-            mController.setDiscovering(true);
-            return mItems;
-        }
-
-        private void updateItems(List<CastDevice> devices) {
-            if (mItems == null) return;
-            Item[] items = null;
-            if (devices != null && !devices.isEmpty()) {
-                // if we are connected, simply show that device
-                for (CastDevice device : devices) {
-                    if (device.state == CastDevice.STATE_CONNECTED) {
-                        final Item item =
-                                new Item(
-                                        R.drawable.ic_cast_connected,
-                                        getDeviceName(device),
-                                        device);
-                        item.line2 = mContext.getString(R.string.quick_settings_connected);
-                        item.canDisconnect = true;
-                        items = new Item[] { item };
-                        break;
-                    }
-                }
-                // otherwise list all available devices, and don't move them around
-                if (items == null) {
-                    for (CastDevice device : devices) {
-                        mVisibleOrder.put(device.id, device);
-                    }
-                    items = new Item[devices.size()];
-                    int i = 0;
-                    for (String id : mVisibleOrder.keySet()) {
-                        final CastDevice device = mVisibleOrder.get(id);
-                        if (!devices.contains(device)) continue;
-                        final Item item =
-                                new Item(R.drawable.ic_cast, getDeviceName(device), device);
-                        if (device.state == CastDevice.STATE_CONNECTING) {
-                            item.line2 = mContext.getString(R.string.quick_settings_connecting);
-                        }
-                        items[i++] = item;
-                    }
-                }
-            }
-            mItems.setItems(items);
-        }
-
-        @Override
-        public void onDetailItemClick(Item item) {
-            if (item == null || item.tag == null) return;
-            MetricsLogger.action(mContext, MetricsEvent.QS_CAST_SELECT);
-            final CastDevice device = (CastDevice) item.tag;
-            mController.startCasting(device);
-        }
-
-        @Override
-        public void onDetailItemDisconnect(Item item) {
-            if (item == null || item.tag == null) return;
-            MetricsLogger.action(mContext, MetricsEvent.QS_CAST_DISCONNECT);
-            final CastDevice device = (CastDevice) item.tag;
-            mController.stopCasting(device);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 698a253..e116f75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -32,9 +32,7 @@
 import android.telephony.SubscriptionManager;
 import android.text.Html;
 import android.text.TextUtils;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowManager.LayoutParams;
 import android.widget.Switch;
 
@@ -49,7 +47,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -71,7 +68,6 @@
 
     private final NetworkController mController;
     private final DataUsageController mDataController;
-    private final CellularDetailAdapter mDetailAdapter;
 
     private final CellSignalCallback mSignalCallback = new CellSignalCallback();
 
@@ -91,7 +87,6 @@
                 statusBarStateController, activityStarter, qsLogger);
         mController = networkController;
         mDataController = mController.getMobileDataController();
-        mDetailAdapter = new CellularDetailAdapter();
         mController.observe(getLifecycle(), mSignalCallback);
     }
 
@@ -106,11 +101,6 @@
     }
 
     @Override
-    public DetailAdapter getDetailAdapter() {
-        return mDetailAdapter;
-    }
-
-    @Override
     public Intent getLongClickIntent() {
         if (getState().state == Tile.STATE_UNAVAILABLE) {
             return new Intent(Settings.ACTION_WIRELESS_SETTINGS);
@@ -161,12 +151,7 @@
 
     @Override
     protected void handleSecondaryClick(@Nullable View view) {
-        if (mDataController.isMobileDataSupported()) {
-            showDetail(true);
-        } else {
-            mActivityStarter
-                    .postStartActivityDismissingKeyguard(getCellularSettingIntent(),0 /* delay */);
-        }
+        handleLongClick(view);
     }
 
     @Override
@@ -298,11 +283,6 @@
             mInfo.airplaneModeEnabled = icon.visible;
             refreshState(mInfo);
         }
-
-        @Override
-        public void setMobileDataEnabled(boolean enabled) {
-            mDetailAdapter.setMobileDataEnabled(enabled);
-        }
     }
 
     static Intent getCellularSettingIntent() {
@@ -314,53 +294,4 @@
         }
         return intent;
     }
-
-    private final class CellularDetailAdapter implements DetailAdapter {
-
-        @Override
-        public CharSequence getTitle() {
-            return mContext.getString(R.string.quick_settings_cellular_detail_title);
-        }
-
-        @Nullable
-        @Override
-        public Boolean getToggleState() {
-            return mDataController.isMobileDataSupported()
-                    ? mDataController.isMobileDataEnabled()
-                    : null;
-        }
-
-        @Override
-        public Intent getSettingsIntent() {
-            return getCellularSettingIntent();
-        }
-
-        @Override
-        public void setToggleState(boolean state) {
-            MetricsLogger.action(mContext, MetricsEvent.QS_CELLULAR_TOGGLE, state);
-            mDataController.setMobileDataEnabled(state);
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return MetricsEvent.QS_DATAUSAGEDETAIL;
-        }
-
-        @Override
-        public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            final DataUsageDetailView v = (DataUsageDetailView) (convertView != null
-                    ? convertView
-                    : LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false));
-            final DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
-            if (info == null) return v;
-            v.bind(info);
-            v.findViewById(R.id.roaming_text).setVisibility(mSignalCallback.mInfo.roaming
-                    ? View.VISIBLE : View.INVISIBLE);
-            return v;
-        }
-
-        public void setMobileDataEnabled(boolean enabled) {
-            fireToggleStateChanged(enabled);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index a33650c..6cff4cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -25,8 +25,6 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
@@ -34,16 +32,10 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ZenRule;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
-import android.util.Slog;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.ViewGroup;
 import android.widget.Switch;
-import android.widget.Toast;
 
 import androidx.annotation.Nullable;
 
@@ -52,13 +44,11 @@
 import com.android.settingslib.notification.EnableZenModeDialog;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.SysUIToast;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
@@ -69,7 +59,6 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.volume.ZenModePanel;
 
 import javax.inject.Inject;
 
@@ -83,14 +72,12 @@
             new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
 
     private final ZenModeController mController;
-    private final DndDetailAdapter mDetailAdapter;
     private final SharedPreferences mSharedPreferences;
     private final SettingObserver mSettingZenDuration;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
 
     private boolean mListening;
-    private boolean mShowingDetail;
 
     @Inject
     public DndTile(
@@ -111,7 +98,6 @@
                 statusBarStateController, activityStarter, qsLogger);
         mController = zenModeController;
         mSharedPreferences = sharedPreferences;
-        mDetailAdapter = new DndDetailAdapter();
         mController.observe(getLifecycle(), mZenCallback);
         mDialogLaunchAnimator = dialogLaunchAnimator;
         mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler,
@@ -142,11 +128,6 @@
     }
 
     @Override
-    public DetailAdapter getDetailAdapter() {
-        return mDetailAdapter;
-    }
-
-    @Override
     public BooleanState newTileState() {
         return new BooleanState();
     }
@@ -225,28 +206,7 @@
 
     @Override
     protected void handleSecondaryClick(@Nullable View view) {
-        if (mController.isVolumeRestricted()) {
-            // Collapse the panels, so the user can see the toast.
-            mHost.collapsePanels();
-            SysUIToast.makeText(mContext, mContext.getString(
-                    com.android.internal.R.string.error_message_change_not_allowed),
-                    Toast.LENGTH_LONG).show();
-            return;
-        }
-        if (!mState.value) {
-            // Because of the complexity of the zen panel, it needs to be shown after
-            // we turn on zen below.
-            mController.addCallback(new ZenModeController.Callback() {
-                @Override
-                public void onZenChanged(int zen) {
-                    mController.removeCallback(this);
-                    showDetail(true);
-                }
-            });
-            mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
-        } else {
-            showDetail(true);
-        }
+        handleLongClick(view);
     }
 
     @Override
@@ -295,9 +255,6 @@
                         R.string.accessibility_quick_settings_dnd);
                 break;
         }
-        if (valueChanged) {
-            fireToggleStateChanged(state.value);
-        }
         state.dualLabelContentDescription = mContext.getResources().getString(
                 R.string.accessibility_quick_settings_open_settings, getTileLabel());
         state.expandedAccessibilityClassName = Switch.class.getName();
@@ -349,146 +306,6 @@
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
         public void onZenChanged(int zen) {
             refreshState(zen);
-            if (isShowingDetail()) {
-                mDetailAdapter.updatePanel();
-            }
-        }
-
-        @Override
-        public void onConfigChanged(ZenModeConfig config) {
-            if (isShowingDetail()) {
-                mDetailAdapter.updatePanel();
-            }
-        }
-    };
-
-    private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener {
-
-        @Nullable
-        private ZenModePanel mZenPanel;
-        private boolean mAuto;
-
-        @Override
-        public CharSequence getTitle() {
-            return mContext.getString(R.string.quick_settings_dnd_label);
-        }
-
-        @Override
-        public Boolean getToggleState() {
-            return mState.value;
-        }
-
-        @Override
-        public Intent getSettingsIntent() {
-            return ZEN_SETTINGS;
-        }
-
-        @Override
-        public void setToggleState(boolean state) {
-            MetricsLogger.action(mContext, MetricsEvent.QS_DND_TOGGLE, state);
-            if (!state) {
-                mController.setZen(ZEN_MODE_OFF, null, TAG);
-                mAuto = false;
-            } else {
-                mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
-            }
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return MetricsEvent.QS_DND_DETAILS;
-        }
-
-        @Override
-        public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            mZenPanel = convertView != null ? (ZenModePanel) convertView
-                    : (ZenModePanel) LayoutInflater.from(context).inflate(
-                            R.layout.zen_mode_panel, parent, false);
-            if (convertView == null) {
-                mZenPanel.init(mController);
-                mZenPanel.addOnAttachStateChangeListener(this);
-                mZenPanel.setCallback(mZenModePanelCallback);
-                mZenPanel.setEmptyState(R.drawable.ic_qs_dnd_detail_empty, R.string.dnd_is_off);
-            }
-            updatePanel();
-            return mZenPanel;
-        }
-
-        private void updatePanel() {
-            if (mZenPanel == null) return;
-            mAuto = false;
-            if (mController.getZen() == ZEN_MODE_OFF) {
-                mZenPanel.setState(ZenModePanel.STATE_OFF);
-            } else {
-                ZenModeConfig config = mController.getConfig();
-                String summary = "";
-                if (config.manualRule != null && config.manualRule.enabler != null) {
-                    summary = getOwnerCaption(config.manualRule.enabler);
-                }
-                for (ZenRule automaticRule : config.automaticRules.values()) {
-                    if (automaticRule.isAutomaticActive()) {
-                        if (summary.isEmpty()) {
-                            summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule,
-                                    automaticRule.name);
-                        } else {
-                            summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule_app);
-                        }
-                    }
-                }
-                if (summary.isEmpty()) {
-                    mZenPanel.setState(ZenModePanel.STATE_MODIFY);
-                } else {
-                    mAuto = true;
-                    mZenPanel.setState(ZenModePanel.STATE_AUTO_RULE);
-                    mZenPanel.setAutoText(summary);
-                }
-            }
-        }
-
-        private String getOwnerCaption(String owner) {
-            final PackageManager pm = mContext.getPackageManager();
-            try {
-                final ApplicationInfo info = pm.getApplicationInfo(owner, 0);
-                if (info != null) {
-                    final CharSequence seq = info.loadLabel(pm);
-                    if (seq != null) {
-                        final String str = seq.toString().trim();
-                        return mContext.getString(R.string.qs_dnd_prompt_app, str);
-                    }
-                }
-            } catch (Throwable e) {
-                Slog.w(TAG, "Error loading owner caption", e);
-            }
-            return "";
-        }
-
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            mShowingDetail = true;
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            mShowingDetail = false;
-            mZenPanel = null;
-        }
-    }
-
-    private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() {
-        @Override
-        public void onPrioritySettings() {
-            mActivityStarter.postStartActivityDismissingKeyguard(
-                    ZEN_PRIORITY_SETTINGS, 0);
-        }
-
-        @Override
-        public void onInteraction() {
-            // noop
-        }
-
-        @Override
-        public void onExpanded(boolean expanded) {
-            // noop
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 9df942d..cd7021e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -411,10 +411,6 @@
         }
         boolean wifiConnected = cb.mEnabled && (cb.mWifiSignalIconId > 0) && (cb.mSsid != null);
         boolean wifiNotConnected = (cb.mWifiSignalIconId > 0) && (cb.mSsid == null);
-        boolean enabledChanging = state.value != cb.mEnabled;
-        if (enabledChanging) {
-            fireToggleStateChanged(cb.mEnabled);
-        }
         if (state.slash == null) {
             state.slash = new SlashState();
             state.slash.rotation = 6;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 0be0619..f4dd415 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -36,7 +36,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
@@ -134,12 +133,6 @@
         return new Intent(Settings.ACTION_PRIVACY_SETTINGS);
     }
 
-    @Nullable
-    @Override
-    public DetailAdapter getDetailAdapter() {
-        return super.getDetailAdapter();
-    }
-
     @Override
     public void onSensorBlockedChanged(int sensor, boolean blocked) {
         if (sensor == getSensorId()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 076ef35..5840a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -150,6 +150,6 @@
     }
 
     protected int getFontSizeDimen() {
-        return R.dimen.qs_detail_item_secondary_text_size;
+        return R.dimen.qs_tile_text_size;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
deleted file mode 100644
index db1b6e6..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.qs.tiles;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.util.Pair;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-
-import javax.inject.Inject;
-
-public class UserTile extends QSTileImpl<State> implements UserInfoController.OnUserInfoChangedListener {
-
-    private final UserSwitcherController mUserSwitcherController;
-    private final UserInfoController mUserInfoController;
-    @Nullable
-    private Pair<String, Drawable> mLastUpdate;
-
-    @Inject
-    public UserTile(
-            QSHost host,
-            @Background Looper backgroundLooper,
-            @Main Handler mainHandler,
-            FalsingManager falsingManager,
-            MetricsLogger metricsLogger,
-            StatusBarStateController statusBarStateController,
-            ActivityStarter activityStarter,
-            QSLogger qsLogger,
-            UserSwitcherController userSwitcherController,
-            UserInfoController userInfoController
-    ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
-                statusBarStateController, activityStarter, qsLogger);
-        mUserSwitcherController = userSwitcherController;
-        mUserInfoController = userInfoController;
-        mUserInfoController.observe(getLifecycle(), this);
-    }
-
-    @Override
-    public State newTileState() {
-        return new QSTile.State();
-    }
-
-    @Override
-    public Intent getLongClickIntent() {
-        return new Intent(Settings.ACTION_USER_SETTINGS);
-    }
-
-    @Override
-    protected void handleClick(@Nullable View view) {
-        showDetail(true);
-    }
-
-    @Override
-    public DetailAdapter getDetailAdapter() {
-        return mUserSwitcherController.mUserDetailAdapter;
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return MetricsEvent.QS_USER_TILE;
-    }
-
-    @Override
-    public CharSequence getTileLabel() {
-        return getState().label;
-    }
-
-    @Override
-    protected void handleUpdateState(State state, Object arg) {
-        final Pair<String, Drawable> p = arg != null ? (Pair<String, Drawable>) arg : mLastUpdate;
-        if (p != null) {
-            state.label = p.first;
-            // TODO: Better content description.
-            state.contentDescription = p.first;
-            state.icon = new Icon() {
-                @Override
-                public Drawable getDrawable(Context context) {
-                    return p.second;
-                }
-            };
-        } else {
-            // TODO: Default state.
-        }
-    }
-
-    @Override
-    public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
-        mLastUpdate = new Pair<>(name, picture);
-        refreshState(mLastUpdate);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index c82ff34..b2be56cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -28,27 +28,22 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.Switch;
 
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.wifi.AccessPoint;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.AlphaControlledSignalTileView;
-import com.android.systemui.qs.QSDetailItems;
-import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSIconViewImpl;
@@ -58,9 +53,6 @@
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIcons;
 import com.android.systemui.statusbar.connectivity.WifiIndicators;
-import com.android.wifitrackerlib.WifiEntry;
-
-import java.util.List;
 
 import javax.inject.Inject;
 
@@ -70,7 +62,6 @@
 
     protected final NetworkController mController;
     private final AccessPointController mWifiController;
-    private final WifiDetailAdapter mDetailAdapter;
     private final QSTile.SignalState mStateBeforeClick = newTileState();
 
     protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback();
@@ -93,7 +84,6 @@
                 statusBarStateController, activityStarter, qsLogger);
         mController = networkController;
         mWifiController = accessPointController;
-        mDetailAdapter = (WifiDetailAdapter) createDetailAdapter();
         mController.observe(getLifecycle(), mSignalCallback);
         mStateBeforeClick.spec = "wifi";
     }
@@ -104,25 +94,6 @@
     }
 
     @Override
-    public void setDetailListening(boolean listening) {
-        if (listening) {
-            mWifiController.addAccessPointCallback(mDetailAdapter);
-        } else {
-            mWifiController.removeAccessPointCallback(mDetailAdapter);
-        }
-    }
-
-    @Override
-    public DetailAdapter getDetailAdapter() {
-        return mDetailAdapter;
-    }
-
-    @Override
-    protected DetailAdapter createDetailAdapter() {
-        return new WifiDetailAdapter();
-    }
-
-    @Override
     public QSIconView createTileView(Context context) {
         return new AlphaControlledSignalTileView(context);
     }
@@ -158,7 +129,6 @@
                     new Intent(Settings.ACTION_WIFI_SETTINGS), 0);
             return;
         }
-        showDetail(true);
         if (!mState.value) {
             mController.setWifiEnabled(true);
         }
@@ -185,11 +155,6 @@
                 && (cb.ssid != null || cb.wifiSignalIconId != WifiIcons.QS_WIFI_NO_NETWORK);
         boolean wifiNotConnected = (cb.ssid == null)
                 && (cb.wifiSignalIconId == WifiIcons.QS_WIFI_NO_NETWORK);
-        boolean enabledChanging = state.value != cb.enabled;
-        if (enabledChanging) {
-            mDetailAdapter.setItemsVisible(cb.enabled);
-            fireToggleStateChanged(cb.enabled);
-        }
         if (state.slash == null) {
             state.slash = new SlashState();
             state.slash.rotation = 6;
@@ -315,150 +280,7 @@
             mInfo.wifiSignalContentDescription = indicators.qsIcon.contentDescription;
             mInfo.isTransient = indicators.isTransient;
             mInfo.statusLabel = indicators.statusLabel;
-            if (isShowingDetail()) {
-                mDetailAdapter.updateItems();
-            }
             refreshState();
         }
     }
-
-    protected class WifiDetailAdapter implements DetailAdapter,
-            AccessPointController.AccessPointCallback, QSDetailItems.Callback {
-
-        @Nullable
-        private QSDetailItems mItems;
-        @Nullable
-        private WifiEntry[] mAccessPoints;
-
-        @Override
-        public CharSequence getTitle() {
-            return mContext.getString(R.string.quick_settings_wifi_label);
-        }
-
-        public Intent getSettingsIntent() {
-            return WIFI_SETTINGS;
-        }
-
-        @Override
-        public Boolean getToggleState() {
-            return mState.value;
-        }
-
-        @Override
-        public void setToggleState(boolean state) {
-            if (DEBUG) Log.d(TAG, "setToggleState " + state);
-            MetricsLogger.action(mContext, MetricsEvent.QS_WIFI_TOGGLE, state);
-            mController.setWifiEnabled(state);
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return MetricsEvent.QS_WIFI_DETAILS;
-        }
-
-        @Override
-        public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null));
-            mAccessPoints = null;
-            mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
-            mItems.setTagSuffix("Wifi");
-            mItems.setCallback(this);
-            mWifiController.scanForAccessPoints(); // updates APs and items
-            setItemsVisible(mState.value);
-            return mItems;
-        }
-
-        @Override
-        public void onAccessPointsChanged(final List<WifiEntry> accessPoints) {
-            mAccessPoints = filterUnreachableAPs(accessPoints);
-
-            updateItems();
-        }
-
-        /** Filter unreachable APs from mAccessPoints */
-        private WifiEntry[] filterUnreachableAPs(List<WifiEntry> unfiltered) {
-            int numReachable = 0;
-            for (WifiEntry ap : unfiltered) {
-                if (isWifiEntryReachable(ap)) numReachable++;
-            }
-            if (numReachable != unfiltered.size()) {
-                WifiEntry[] accessPoints = new WifiEntry[numReachable];
-                int i = 0;
-                for (WifiEntry ap : unfiltered) {
-                    if (isWifiEntryReachable(ap)) accessPoints[i++] = ap;
-                }
-                return accessPoints;
-            }
-            return unfiltered.toArray(new WifiEntry[0]);
-        }
-
-        @Override
-        public void onSettingsActivityTriggered(Intent settingsIntent) {
-            mActivityStarter.postStartActivityDismissingKeyguard(settingsIntent, 0);
-        }
-
-        @Override
-        public void onDetailItemClick(Item item) {
-            if (item == null || item.tag == null) return;
-            final WifiEntry ap = (WifiEntry) item.tag;
-            if (ap.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
-                if (mWifiController.connect(ap)) {
-                    mHost.collapsePanels();
-                }
-            }
-            showDetail(false);
-        }
-
-        @Override
-        public void onDetailItemDisconnect(Item item) {
-            // noop
-        }
-
-        public void setItemsVisible(boolean visible) {
-            if (mItems == null) return;
-            mItems.setItemsVisible(visible);
-        }
-
-        private void updateItems() {
-            if (mItems == null) return;
-            if ((mAccessPoints != null && mAccessPoints.length > 0)
-                    || !mSignalCallback.mInfo.enabled) {
-                fireScanStateChanged(false);
-            } else {
-                fireScanStateChanged(true);
-            }
-
-            // Wi-Fi is off
-            if (!mSignalCallback.mInfo.enabled) {
-                mItems.setEmptyState(WifiIcons.QS_WIFI_NO_NETWORK,
-                        R.string.wifi_is_off);
-                mItems.setItems(null);
-                return;
-            }
-
-            // No available access points
-            mItems.setEmptyState(WifiIcons.QS_WIFI_NO_NETWORK,
-                    R.string.quick_settings_wifi_detail_empty_text);
-
-            // Build the list
-            Item[] items = null;
-            if (mAccessPoints != null) {
-                items = new Item[mAccessPoints.length];
-                for (int i = 0; i < mAccessPoints.length; i++) {
-                    final WifiEntry ap = mAccessPoints[i];
-                    final Item item = new Item(mWifiController.getIcon(ap), ap.getSsid(), ap);
-                    item.line2 = ap.getSummary();
-                    item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
-                            ? R.drawable.qs_ic_wifi_lock
-                            : -1;
-                    items[i] = item;
-                }
-            }
-            mItems.setItems(items);
-        }
-    }
-
-    private static boolean isWifiEntryReachable(WifiEntry ap) {
-        return ap.getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 7c8f4b15..8c8c5c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -24,11 +24,13 @@
 import android.view.LayoutInflater
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.QSUserSwitcherEvent
 import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import javax.inject.Inject
@@ -43,6 +45,7 @@
     private val activityStarter: ActivityStarter,
     private val falsingManager: FalsingManager,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val uiEventLogger: UiEventLogger,
     private val dialogFactory: (Context) -> SystemUIDialog
 ) {
 
@@ -51,12 +54,14 @@
         userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
         activityStarter: ActivityStarter,
         falsingManager: FalsingManager,
-        dialogLaunchAnimator: DialogLaunchAnimator
+        dialogLaunchAnimator: DialogLaunchAnimator,
+        uiEventLogger: UiEventLogger
     ) : this(
         userDetailViewAdapterProvider,
         activityStarter,
         falsingManager,
         dialogLaunchAnimator,
+        uiEventLogger,
         { SystemUIDialog(it) }
     )
 
@@ -76,10 +81,13 @@
             setCanceledOnTouchOutside(true)
 
             setTitle(R.string.qs_user_switch_dialog_title)
-            setPositiveButton(R.string.quick_settings_done, null)
+            setPositiveButton(R.string.quick_settings_done) { _, _ ->
+                uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
+            }
             setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ ->
                 if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
                     dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+                    uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
                     activityStarter.postStartActivityDismissingKeyguard(
                         USER_SETTINGS_INTENT,
                         0
@@ -95,6 +103,7 @@
             adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
 
             dialogLaunchAnimator.showFromView(this, view)
+            uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
             adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 30456a8..50765f2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -51,7 +51,9 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.media.MediaActionSound;
+import android.media.AudioAttributes;
+import android.media.AudioSystem;
+import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -93,6 +95,7 @@
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.io.File;
 import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
@@ -248,7 +251,7 @@
     private final WindowManager mWindowManager;
     private final WindowManager.LayoutParams mWindowLayoutParams;
     private final AccessibilityManager mAccessibilityManager;
-    private final MediaActionSound mCameraSound;
+    private final MediaPlayer mCameraSound;
     private final ScrollCaptureClient mScrollCaptureClient;
     private final PhoneWindow mWindow;
     private final DisplayManager mDisplayManager;
@@ -331,8 +334,13 @@
         reloadAssets();
 
         // Setup the Camera shutter sound
-        mCameraSound = new MediaActionSound();
-        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+        mCameraSound = MediaPlayer.create(mContext,
+                Uri.fromFile(new File(mContext.getResources().getString(
+                        com.android.internal.R.string.config_cameraShutterSound))), null,
+                new AudioAttributes.Builder()
+                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                        .build(), AudioSystem.newAudioSessionId());
 
         mCopyBroadcastReceiver = new BroadcastReceiver() {
             @Override
@@ -430,7 +438,9 @@
     void releaseContext() {
         mContext.unregisterReceiver(mCopyBroadcastReceiver);
         mContext.release();
-        mCameraSound.release();
+        if (mCameraSound != null) {
+            mCameraSound.release();
+        }
         mBgExecutor.shutdownNow();
     }
 
@@ -806,7 +816,9 @@
      */
     private void saveScreenshotAndToast(Consumer<Uri> finisher) {
         // Play the shutter sound to notify that we've taken a screenshot
-        mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+        if (mCameraSound != null) {
+            mCameraSound.start();
+        }
 
         saveScreenshotInWorkerThread(
                 /* onComplete */ finisher,
@@ -840,7 +852,9 @@
                 mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
 
         // Play the shutter sound to notify that we've taken a screenshot
-        mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+        if (mCameraSound != null) {
+            mCameraSound.start();
+        }
 
         if (DEBUG_ANIM) {
             Log.d(TAG, "starting post-screenshot animation");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 2f5eaa6..b355b05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -453,7 +453,9 @@
         /** @see IStatusBar#updateMediaTapToTransferReceiverDisplay */
         default void updateMediaTapToTransferReceiverDisplay(
                 @StatusBarManager.MediaTransferReceiverState int displayState,
-                @NonNull MediaRoute2Info routeInfo) {}
+                @NonNull MediaRoute2Info routeInfo,
+                @Nullable Icon appIcon,
+                @Nullable CharSequence appName) {}
     }
 
     public CommandQueue(Context context) {
@@ -1208,10 +1210,14 @@
     @Override
     public void updateMediaTapToTransferReceiverDisplay(
             int displayState,
-            MediaRoute2Info routeInfo) {
+            @NonNull MediaRoute2Info routeInfo,
+            @Nullable Icon appIcon,
+            @Nullable CharSequence appName) {
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = displayState;
         args.arg2 = routeInfo;
+        args.arg3 = appIcon;
+        args.arg4 = appName;
         mHandler.obtainMessage(MSG_MEDIA_TRANSFER_RECEIVER_STATE, args).sendToTarget();
     }
 
@@ -1629,9 +1635,11 @@
                     args = (SomeArgs) msg.obj;
                     int receiverDisplayState = (int) args.arg1;
                     MediaRoute2Info receiverRouteInfo = (MediaRoute2Info) args.arg2;
+                    Icon appIcon = (Icon) args.arg3;
+                    appName = (CharSequence) args.arg4;
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).updateMediaTapToTransferReceiverDisplay(
-                                receiverDisplayState, receiverRouteInfo);
+                                receiverDisplayState, receiverRouteInfo, appIcon, appName);
                     }
                     args.recycle();
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 8e6cf36..4d933d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -31,6 +31,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;
 
+import java.util.ArrayList;
+
 
 /**
  * The view in the statusBar that contains part of the heads-up information
@@ -161,8 +163,8 @@
         return mIconDrawingRect;
     }
 
-    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        mTextView.setTextColor(DarkIconDispatcher.getTint(areas, this, tint));
     }
 
     public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index e19fd7a..01bdb40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -29,7 +29,9 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
@@ -42,10 +44,13 @@
 import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+
 /**
  * This class handles listening to notification updates and passing them along to
  * NotificationPresenter to be displayed to the user.
  */
+@SysUISingleton
 @SuppressLint("OverrideAbstract")
 public class NotificationListener extends NotificationListenerWithPlugins {
     private static final String TAG = "NotificationListener";
@@ -66,11 +71,14 @@
     /**
      * Injected constructor. See {@link StatusBarModule}.
      */
+    @Inject
     public NotificationListener(
             Context context,
             NotificationManager notificationManager,
             SystemClock systemClock,
-            @Main Executor mainExecutor) {
+            @Main Executor mainExecutor,
+            PluginManager pluginManager) {
+        super(pluginManager);
         mContext = context;
         mNotificationManager = notificationManager;
         mSystemClock = systemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 4a7606c..72c4ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -37,6 +37,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Parcelable;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
@@ -60,6 +61,7 @@
 import com.android.systemui.util.drawable.DrawableSize;
 
 import java.text.NumberFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 
 public class StatusBarIconView extends AnimatedImageView implements StatusIconDisplayable {
@@ -370,10 +372,13 @@
         }
         Drawable drawable;
         try {
+            Trace.beginSection("StatusBarIconView#updateDrawable()");
             drawable = getIcon(mIcon);
         } catch (OutOfMemoryError e) {
             Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
             return false;
+        } finally {
+            Trace.endSection();
         }
 
         if (drawable == null) {
@@ -961,8 +966,8 @@
     }
 
     @Override
-    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        int areaTint = getTint(area, this, tint);
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        int areaTint = getTint(areas, this, tint);
         ColorStateList color = ColorStateList.valueOf(areaTint);
         setImageTintList(color);
         setDecorColor(areaTint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 68dcdd9..465ab93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar;
 
 import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.plugins.DarkIconDispatcher.isInArea;
+import static com.android.systemui.plugins.DarkIconDispatcher.isInAreas;
 import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
 import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
 import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
@@ -40,6 +40,8 @@
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 
+import java.util.ArrayList;
+
 public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
         StatusIconDisplayable {
     private static final String TAG = "StatusBarMobileView";
@@ -222,11 +224,11 @@
     }
 
     @Override
-    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        float intensity = isInArea(area, this) ? darkIntensity : 0;
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        float intensity = isInAreas(areas, this) ? darkIntensity : 0;
         mMobileDrawable.setTintList(
                 ColorStateList.valueOf(mDualToneHandler.getSingleColor(intensity)));
-        ColorStateList color = ColorStateList.valueOf(getTint(area, this, tint));
+        ColorStateList color = ColorStateList.valueOf(getTint(areas, this, tint));
         mIn.setImageTintList(color);
         mOut.setImageTintList(color);
         mMobileType.setImageTintList(color);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index 6dbcc44..a6986d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar;
 
 import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.plugins.DarkIconDispatcher.isInArea;
 import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
 import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
 import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
@@ -37,6 +36,8 @@
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
+import java.util.ArrayList;
+
 /**
  * Start small: StatusBarWifiView will be able to layout from a WifiIconState
  */
@@ -235,8 +236,8 @@
     }
 
     @Override
-    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        int areaTint = getTint(area, this, tint);
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        int areaTint = getTint(areas, this, tint);
         ColorStateList color = ColorStateList.valueOf(areaTint);
         mWifiIcon.setImageTintList(color);
         mIn.setImageTintList(color);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt
new file mode 100644
index 0000000..46c1abb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.phone.StatusBar
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface StartStatusBarModule {
+    /** Start the StatusBar   */
+    @Binds
+    @IntoMap
+    @ClassKey(StatusBar::class)
+    abstract fun bindsStatusBar(statusBar: StatusBar): CoreStartable
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index e3d0d98..c687e82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.dagger;
 
 import android.app.IActivityManager;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.os.Handler;
 import android.service.dreams.IDreamManager;
@@ -38,7 +37,6 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.MediaArtworkProcessor;
 import com.android.systemui.statusbar.NotificationClickNotifier;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -167,18 +165,6 @@
     /** */
     @SysUISingleton
     @Provides
-    static NotificationListener provideNotificationListener(
-            Context context,
-            NotificationManager notificationManager,
-            SystemClock systemClock,
-            @Main Executor mainExecutor) {
-        return new NotificationListener(
-                context, notificationManager, systemClock, mainExecutor);
-    }
-
-    /** */
-    @SysUISingleton
-    @Provides
     static SmartReplyController provideSmartReplyController(
             DumpManager dumpManager,
             NotificationVisibilityProvider visibilityProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index e739b9f..e3ebef9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -204,7 +204,7 @@
     static VisualStabilityManager provideVisualStabilityManager(
             NotificationEntryManager notificationEntryManager,
             VisualStabilityProvider visualStabilityProvider,
-            Handler handler,
+            @Main Handler handler,
             StatusBarStateController statusBarStateController,
             WakefulnessLifecycle wakefulnessLifecycle,
             DumpManager dumpManager) {
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 6a78370c..d5d1cea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -19,7 +19,6 @@
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 
 import android.annotation.IntDef;
-import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.fingerprint.FingerprintManager;
@@ -45,6 +44,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.Dumpable;
+import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -163,7 +163,7 @@
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final SessionTracker mSessionTracker;
-    private final Context mContext;
+    private final int mConsecutiveFpFailureThreshold;
     private final int mWakeUpDelay;
     private int mMode;
     private BiometricSourceType mBiometricType;
@@ -266,7 +266,7 @@
     private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
 
     @Inject
-    public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
+    public BiometricUnlockController(DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
             ShadeController shadeController,
             NotificationShadeWindowController notificationShadeWindowController,
@@ -284,7 +284,6 @@
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SessionTracker sessionTracker,
             LatencyTracker latencyTracker) {
-        mContext = context;
         mPowerManager = powerManager;
         mShadeController = shadeController;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -302,6 +301,8 @@
         mKeyguardStateController = keyguardStateController;
         mHandler = handler;
         mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze);
+        mConsecutiveFpFailureThreshold = resources.getInteger(
+                R.integer.fp_consecutive_failure_time_ms);
         mKeyguardBypassController = keyguardBypassController;
         mKeyguardBypassController.setUnlockController(this);
         mMetricsLogger = metricsLogger;
@@ -666,8 +667,7 @@
         if (biometricSourceType == BiometricSourceType.FINGERPRINT
                 && mUpdateMonitor.isUdfpsSupported()) {
             long currUptimeMillis = SystemClock.uptimeMillis();
-            if (currUptimeMillis - mLastFpFailureUptimeMillis
-                    < (mStatusBarStateController.isDozing() ? 3500 : 2000)) {
+            if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) {
                 mNumConsecutiveFpFailures += 1;
             } else {
                 mNumConsecutiveFpFailures = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index d06de75..150da16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -30,6 +30,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 import javax.inject.Inject;
 
@@ -40,7 +41,7 @@
         LightBarTransitionsController.DarkIntensityApplier {
 
     private final LightBarTransitionsController mTransitionsController;
-    private final Rect mTintArea = new Rect();
+    private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();
 
     private int mIconTint = DEFAULT_ICON_TINT;
@@ -69,14 +70,14 @@
 
     public void addDarkReceiver(DarkReceiver receiver) {
         mReceivers.put(receiver, receiver);
-        receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+        receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
     }
 
     public void addDarkReceiver(ImageView imageView) {
         DarkReceiver receiver = (area, darkIntensity, tint) -> imageView.setImageTintList(
-                ColorStateList.valueOf(getTint(mTintArea, imageView, mIconTint)));
+                ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint)));
         mReceivers.put(imageView, receiver);
-        receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+        receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
     }
 
     public void removeDarkReceiver(DarkReceiver object) {
@@ -88,23 +89,23 @@
     }
 
     public void applyDark(DarkReceiver object) {
-        mReceivers.get(object).onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+        mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
     }
 
     /**
      * Sets the dark area so {@link #applyDark} only affects the icons in the specified area.
      *
-     * @param darkArea the area in which icons should change it's tint, in logical screen
-     *                 coordinates
+     * @param darkAreas the areas in which icons should change it's tint, in logical screen
+     *                  coordinates
      */
-    public void setIconsDarkArea(Rect darkArea) {
-        if (darkArea == null && mTintArea.isEmpty()) {
+    public void setIconsDarkArea(ArrayList<Rect> darkAreas) {
+        if (darkAreas == null && mTintAreas.isEmpty()) {
             return;
         }
-        if (darkArea == null) {
-            mTintArea.setEmpty();
-        } else {
-            mTintArea.set(darkArea);
+
+        mTintAreas.clear();
+        if (darkAreas != null) {
+            mTintAreas.addAll(darkAreas);
         }
         applyIconTint();
     }
@@ -124,7 +125,7 @@
 
     private void applyIconTint() {
         for (int i = 0; i < mReceivers.size(); i++) {
-            mReceivers.valueAt(i).onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+            mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
         }
     }
 
@@ -133,6 +134,6 @@
         pw.println("DarkIconDispatcher: ");
         pw.println("  mIconTint: 0x" + Integer.toHexString(mIconTint));
         pw.println("  mDarkIntensity: " + mDarkIntensity + "f");
-        pw.println("  mTintArea: " + mTintArea);
+        pw.println("  mTintAreas: " + mTintAreas);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index ee51efb..6dbbf0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -315,14 +315,14 @@
     }
 
     @Override
-    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        setColor(DarkIconDispatcher.getTint(area, mStatusIcons, tint));
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
 
         if (mWifiView != null) {
-            mWifiView.onDarkChanged(area, darkIntensity, tint);
+            mWifiView.onDarkChanged(areas, darkIntensity, tint);
         }
         for (StatusBarMobileView view : mMobileViews) {
-            view.onDarkChanged(area, darkIntensity, tint);
+            view.onDarkChanged(areas, darkIntensity, tint);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f421d23..9863a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -16,34 +16,37 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW;
+
 import android.graphics.Rect;
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
+import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.util.ViewController;
 
+import java.util.Optional;
+import java.util.ArrayList;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * Controls the appearance of heads up notifications in the icon area and the header itself.
@@ -69,8 +72,8 @@
     private final CommandQueue mCommandQueue;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
 
-    private View mClockView;
-    private View mOperatorNameView;
+    private final View mClockView;
+    private final Optional<View> mOperatorNameViewOptional;
 
     @VisibleForTesting
     float mExpandedHeight;
@@ -86,45 +89,24 @@
                 }
             };
     private boolean mAnimationsEnabled = true;
-    private KeyguardStateController mKeyguardStateController;
-
-    @Inject
-    public HeadsUpAppearanceController(
-            NotificationIconAreaController notificationIconAreaController,
-            HeadsUpManagerPhone headsUpManager,
-            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
-            SysuiStatusBarStateController statusBarStateController,
-            KeyguardBypassController keyguardBypassController,
-            KeyguardStateController keyguardStateController,
-            NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue,
-            NotificationPanelViewController notificationPanelViewController,
-            @RootView PhoneStatusBarView statusBarView) {
-        this(notificationIconAreaController, headsUpManager, statusBarStateController,
-                keyguardBypassController, wakeUpCoordinator, keyguardStateController,
-                commandQueue, notificationStackScrollLayoutController,
-                notificationPanelViewController,
-                // TODO(b/205609837): We should have the StatusBarFragmentComponent provide these
-                //  four views, and then we can delete this constructor and just use the one below
-                //  (which also removes the undesirable @VisibleForTesting).
-                statusBarView.findViewById(R.id.heads_up_status_bar_view),
-                statusBarView.findViewById(R.id.clock),
-                statusBarView.findViewById(R.id.operator_name_frame));
-    }
+    private final KeyguardStateController mKeyguardStateController;
 
     @VisibleForTesting
+    @Inject
     public HeadsUpAppearanceController(
             NotificationIconAreaController notificationIconAreaController,
             HeadsUpManagerPhone headsUpManager,
             StatusBarStateController stateController,
             KeyguardBypassController bypassController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
+            DarkIconDispatcher darkIconDispatcher,
             KeyguardStateController keyguardStateController,
             CommandQueue commandQueue,
             NotificationStackScrollLayoutController stackScrollerController,
             NotificationPanelViewController notificationPanelViewController,
             HeadsUpStatusBarView headsUpStatusBarView,
-            View clockView,
-            View operatorNameView) {
+            Clock clockView,
+            @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
         super(headsUpStatusBarView);
         mNotificationIconAreaController = notificationIconAreaController;
         mHeadsUpManager = headsUpManager;
@@ -141,8 +123,8 @@
         mNotificationPanelViewController = notificationPanelViewController;
         mStackScrollerController.setHeadsUpAppearanceController(this);
         mClockView = clockView;
-        mOperatorNameView = operatorNameView;
-        mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
+        mOperatorNameViewOptional = operatorNameViewOptional;
+        mDarkIconDispatcher = darkIconDispatcher;
 
         mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
             @Override
@@ -232,14 +214,10 @@
                 mView.setVisibility(View.VISIBLE);
                 show(mView);
                 hide(mClockView, View.INVISIBLE);
-                if (mOperatorNameView != null) {
-                    hide(mOperatorNameView, View.INVISIBLE);
-                }
+                mOperatorNameViewOptional.ifPresent(view -> hide(view, View.INVISIBLE));
             } else {
                 show(mClockView);
-                if (mOperatorNameView != null) {
-                    show(mOperatorNameView);
-                }
+                mOperatorNameViewOptional.ifPresent(this::show);
                 hide(mView, View.GONE, () -> {
                     updateParentClipping(true /* shouldClip */);
                 });
@@ -392,8 +370,8 @@
     }
 
     @Override
-    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        mView.onDarkChanged(area, darkIntensity, tint);
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        mView.onDarkChanged(areas, darkIntensity, tint);
     }
 
     public void onStateChanged() {
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 565b2d3..95a2a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -41,6 +41,7 @@
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -115,7 +116,7 @@
             BouncerExpansionCallback expansionCallback,
             KeyguardStateController keyguardStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardBypassController keyguardBypassController, Handler handler,
+            KeyguardBypassController keyguardBypassController, @Main Handler handler,
             KeyguardSecurityModel keyguardSecurityModel,
             KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
         mContext = context;
@@ -647,7 +648,7 @@
                 DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
                 KeyguardStateController keyguardStateController,
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
-                KeyguardBypassController keyguardBypassController, Handler handler,
+                KeyguardBypassController keyguardBypassController, @Main Handler handler,
                 KeyguardSecurityModel keyguardSecurityModel,
                 KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
             mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
index 817b86b..9bdefcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
@@ -20,7 +20,6 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
@@ -33,13 +32,15 @@
 
     private static final String TAG = "KeyguardEnvironmentImpl";
 
-    private final NotificationLockscreenUserManager mLockscreenUserManager =
-            Dependency.get(NotificationLockscreenUserManager.class);
-    private final DeviceProvisionedController mDeviceProvisionedController =
-            Dependency.get(DeviceProvisionedController.class);
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final DeviceProvisionedController mDeviceProvisionedController;
 
     @Inject
-    public KeyguardEnvironmentImpl() {
+    public KeyguardEnvironmentImpl(
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            DeviceProvisionedController deviceProvisionedController) {
+        mLockscreenUserManager = notificationLockscreenUserManager;
+        mDeviceProvisionedController = deviceProvisionedController;
     }
 
     @Override  // NotificationEntryManager.KeyguardEnvironment
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index b8e9875..65173a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -50,6 +50,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 /**
  * The header group on Keyguard.
@@ -60,7 +61,7 @@
     private static final int LAYOUT_CUTOUT = 1;
     private static final int LAYOUT_NO_CUTOUT = 2;
 
-    private final Rect mEmptyRect = new Rect(0, 0, 0, 0);
+    private final ArrayList<Rect> mEmptyTintRect = new ArrayList<>();
 
     private boolean mShowPercentAvailable;
     private boolean mBatteryCharging;
@@ -476,14 +477,14 @@
             iconManager.setTint(iconColor);
         }
 
-        applyDarkness(R.id.battery, mEmptyRect, intensity, iconColor);
-        applyDarkness(R.id.clock, mEmptyRect, intensity, iconColor);
+        applyDarkness(R.id.battery, mEmptyTintRect, intensity, iconColor);
+        applyDarkness(R.id.clock, mEmptyTintRect, intensity, iconColor);
     }
 
-    private void applyDarkness(int id, Rect tintArea, float intensity, int color) {
+    private void applyDarkness(int id, ArrayList<Rect> tintAreas, float intensity, int color) {
         View v = findViewById(id);
         if (v instanceof DarkReceiver) {
-            ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color);
+            ((DarkReceiver) v).onDarkChanged(tintAreas, intensity, color);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 88ae0db..4082db7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -25,6 +25,7 @@
 
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.view.InsetsFlags;
 import android.view.ViewDebug;
 import android.view.WindowInsetsController.Appearance;
@@ -41,6 +42,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 import javax.inject.Inject;
 
@@ -214,27 +216,23 @@
 
     private void updateStatus() {
         final int numStacks = mAppearanceRegions.length;
-        int numLightStacks = 0;
-
-        // We can only have maximum one light stack.
-        int indexLightStack = -1;
+        final ArrayList<Rect> lightBarBounds = new ArrayList<>();
 
         for (int i = 0; i < numStacks; i++) {
-            if (isLight(mAppearanceRegions[i].getAppearance(), mStatusBarMode,
-                    APPEARANCE_LIGHT_STATUS_BARS)) {
-                numLightStacks++;
-                indexLightStack = i;
+            final AppearanceRegion ar = mAppearanceRegions[i];
+            if (isLight(ar.getAppearance(), mStatusBarMode, APPEARANCE_LIGHT_STATUS_BARS)) {
+                lightBarBounds.add(ar.getBounds());
             }
         }
 
         // If no one is light, all icons become white.
-        if (numLightStacks == 0) {
+        if (lightBarBounds.isEmpty()) {
             mStatusBarIconController.getTransitionsController().setIconsDark(
                     false, animateChange());
         }
 
         // If all stacks are light, all icons get dark.
-        else if (numLightStacks == numStacks) {
+        else if (lightBarBounds.size() == numStacks) {
             mStatusBarIconController.setIconsDarkArea(null);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
 
@@ -242,8 +240,7 @@
 
         // Not the same for every stack, magic!
         else {
-            mStatusBarIconController.setIconsDarkArea(
-                    mAppearanceRegions[indexLightStack].getBounds());
+            mStatusBarIconController.setIconsDarkArea(lightBarBounds);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 357a12b..324d47e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -29,9 +29,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.FooterActionsView;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.user.UserSwitchDialogController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -44,7 +42,6 @@
 public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
     private final UserManager mUserManager;
     private final UserSwitcherController mUserSwitcherController;
-    private final QSDetailDisplayer mQsDetailDisplayer;
     private final FalsingManager mFalsingManager;
     private final UserSwitchDialogController mUserSwitchDialogController;
     private final ActivityStarter mActivityStarter;
@@ -66,17 +63,8 @@
                 mActivityStarter.startActivity(intent, true /* dismissShade */,
                         ActivityLaunchAnimator.Controller.fromView(v, null),
                         true /* showOverlockscreenwhenlocked */);
-            } else if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
-                mUserSwitchDialogController.showDialog(v);
             } else {
-                View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView;
-
-                int[] tmpInt = new int[2];
-                center.getLocationInWindow(tmpInt);
-                tmpInt[0] += center.getWidth() / 2;
-                tmpInt[1] += center.getHeight() / 2;
-
-                mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]);
+                mUserSwitchDialogController.showDialog(v);
             }
         }
     };
@@ -85,7 +73,6 @@
     public static class Factory {
         private final UserManager mUserManager;
         private final UserSwitcherController mUserSwitcherController;
-        private final QSDetailDisplayer mQsDetailDisplayer;
         private final FalsingManager mFalsingManager;
         private final UserSwitchDialogController mUserSwitchDialogController;
         private final ActivityStarter mActivityStarter;
@@ -93,12 +80,11 @@
 
         @Inject
         public Factory(UserManager userManager, UserSwitcherController userSwitcherController,
-                QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager,
+                FalsingManager falsingManager,
                 UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags,
                 ActivityStarter activityStarter) {
             mUserManager = userManager;
             mUserSwitcherController = userSwitcherController;
-            mQsDetailDisplayer = qsDetailDisplayer;
             mFalsingManager = falsingManager;
             mUserSwitchDialogController = userSwitchDialogController;
             mActivityStarter = activityStarter;
@@ -107,20 +93,19 @@
 
         public MultiUserSwitchController create(FooterActionsView view) {
             return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch),
-                    mUserManager, mUserSwitcherController, mQsDetailDisplayer,
+                    mUserManager, mUserSwitcherController,
                     mFalsingManager, mUserSwitchDialogController, mFeatureFlags,
                     mActivityStarter);
         }
     }
 
     private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
-            UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer,
+            UserSwitcherController userSwitcherController,
             FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController,
             FeatureFlags featureFlags, ActivityStarter activityStarter) {
         super(view);
         mUserManager = userManager;
         mUserSwitcherController = userSwitcherController;
-        mQsDetailDisplayer = qsDetailDisplayer;
         mFalsingManager = falsingManager;
         mUserSwitchDialogController = userSwitchDialogController;
         mFeatureFlags = featureFlags;
@@ -143,10 +128,6 @@
         mView.setOnClickListener(null);
     }
 
-    protected DetailAdapter getUserDetailAdapter() {
-        return mUserSwitcherController.mUserDetailAdapter;
-    }
-
     private void registerListener() {
         if (mUserManager.isUserSwitcherEnabled() && mUserListener == null) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 8bababf..ca6e67e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -28,7 +28,7 @@
 import android.util.Log;
 
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -48,11 +48,14 @@
 import java.util.List;
 import java.util.Objects;
 
+import javax.inject.Inject;
+
 /**
  * A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
  * and {@link HeadsUpManager}. In particular, this class deals with keeping
  * the correct notification in a group alerting based off the group suppression and alertOverride.
  */
+@SysUISingleton
 public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
         StateListener {
 
@@ -74,8 +77,7 @@
 
     private HeadsUpManager mHeadsUpManager;
     private final RowContentBindStage mRowContentBindStage;
-    private final NotificationGroupManagerLegacy mGroupManager =
-            Dependency.get(NotificationGroupManagerLegacy.class);
+    private final NotificationGroupManagerLegacy mGroupManager;
 
     private NotificationEntryManager mEntryManager;
 
@@ -84,9 +86,14 @@
     /**
      * Injected constructor. See {@link StatusBarPhoneModule}.
      */
-    public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) {
-        Dependency.get(StatusBarStateController.class).addCallback(this);
+    @Inject
+    public NotificationGroupAlertTransferHelper(
+            RowContentBindStage bindStage,
+            StatusBarStateController statusBarStateController,
+            NotificationGroupManagerLegacy notificationGroupManagerLegacy) {
         mRowContentBindStage = bindStage;
+        mGroupManager = notificationGroupManagerLegacy;
+        statusBarStateController.addCallback(this);
     }
 
     /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index c361300..e70c81d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -83,7 +83,7 @@
     private NotificationIconContainer mNotificationIcons;
     private NotificationIconContainer mShelfIcons;
     private NotificationIconContainer mAodIcons;
-    private final Rect mTintArea = new Rect();
+    private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private Context mContext;
 
     private final DemoModeController mDemoModeController;
@@ -240,17 +240,14 @@
      * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
      * Sets the color that should be used to tint any icons in the notification area.
      *
-     * @param tintArea the area in which to tint the icons, specified in screen coordinates
+     * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
      * @param darkIntensity
      */
-    public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) {
-        if (tintArea == null) {
-            mTintArea.setEmpty();
-        } else {
-            mTintArea.set(tintArea);
-        }
+    public void onDarkChanged(ArrayList<Rect> tintAreas, float darkIntensity, int iconTint) {
+        mTintAreas.clear();
+        mTintAreas.addAll(tintAreas);
 
-        if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
+        if (DarkIconDispatcher.isInAreas(tintAreas, mNotificationIconArea)) {
             mIconTint = iconTint;
         }
 
@@ -489,7 +486,7 @@
         int color = StatusBarIconView.NO_COLOR;
         boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mContrastColorUtil);
         if (colorize) {
-            color = DarkIconDispatcher.getTint(mTintArea, v, tint);
+            color = DarkIconDispatcher.getTint(mTintAreas, v, tint);
         }
         v.setStaticDrawableColor(color);
         v.setDecorColor(tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
index c68d39b..3811689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -22,7 +22,6 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.plugins.NotificationListenerController;
 import com.android.systemui.plugins.NotificationListenerController.NotificationProvider;
 import com.android.systemui.plugins.PluginListener;
@@ -30,6 +29,8 @@
 
 import java.util.ArrayList;
 
+import javax.inject.Inject;
+
 /**
  * A version of NotificationListenerService that passes all info to
  * any plugins connected. Also allows those plugins the chance to cancel
@@ -40,19 +41,25 @@
 
     private ArrayList<NotificationListenerController> mPlugins = new ArrayList<>();
     private boolean mConnected;
+    private PluginManager mPluginManager;
+
+    @Inject
+    public NotificationListenerWithPlugins(PluginManager pluginManager) {
+        super();
+        mPluginManager = pluginManager;
+    }
 
     @Override
     public void registerAsSystemService(Context context, ComponentName componentName,
             int currentUser) throws RemoteException {
         super.registerAsSystemService(context, componentName, currentUser);
-        Dependency.get(PluginManager.class).addPluginListener(this,
-                NotificationListenerController.class);
+        mPluginManager.addPluginListener(this, NotificationListenerController.class);
     }
 
     @Override
     public void unregisterAsSystemService() throws RemoteException {
         super.unregisterAsSystemService();
-        Dependency.get(PluginManager.class).removePluginListener(this);
+        mPluginManager.removePluginListener(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 62a96ad..d93c013 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -113,7 +113,6 @@
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.Interpolators;
@@ -147,12 +146,10 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
@@ -326,7 +323,6 @@
     private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
     private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
     private final IdleViewComponent.Factory mIdleViewComponentFactory;
-    private final QSDetailDisplayer mQSDetailDisplayer;
     private final FragmentService mFragmentService;
     private final ScrimController mScrimController;
     private final PrivacyDotViewController mPrivacyDotViewController;
@@ -774,7 +770,6 @@
             CommunalViewComponent.Factory communalViewComponentFactory,
             IdleViewComponent.Factory idleViewComponentFactory,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
-            QSDetailDisplayer qsDetailDisplayer,
             NotificationGroupManagerLegacy groupManager,
             NotificationIconAreaController notificationIconAreaController,
             AuthController authController,
@@ -804,6 +799,7 @@
             ControlsComponent controlsComponent,
             InteractionJankMonitor interactionJankMonitor,
             QsFrameTranslateController qsFrameTranslateController,
+            SysUiState sysUiState,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
         super(view,
                 falsingManager,
@@ -848,7 +844,6 @@
         mContentResolver = contentResolver;
         mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
         mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
-        mQSDetailDisplayer = qsDetailDisplayer;
         mFragmentService = fragmentService;
         mSettingsChangeObserver = new SettingsChangeObserver(handler);
         mShouldUseSplitNotificationShade =
@@ -876,8 +871,7 @@
         mUiExecutor = uiExecutor;
         mSecureSettings = secureSettings;
         mInteractionJankMonitor = interactionJankMonitor;
-        // TODO: inject via dagger instead of Dependency
-        mSysUiState = Dependency.get(SysUiState.class);
+        mSysUiState = sysUiState;
         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
             if (mQs != null) {
                 mQs.animateHeaderSlidingOut();
@@ -1137,7 +1131,6 @@
                     mKeyguardQsUserSwitchComponentFactory.build(userAvatarView);
             mKeyguardQsUserSwitchController =
                     userSwitcherComponent.getKeyguardQsUserSwitchController();
-            mKeyguardQsUserSwitchController.setNotificationPanelViewController(this);
             mKeyguardQsUserSwitchController.init();
             mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true);
         } else if (keyguardUserSwitcherView != null) {
@@ -1849,18 +1842,6 @@
         }
     }
 
-    public void expandWithQsDetail(DetailAdapter qsDetailAdapter) {
-        traceQsJank(true /* startTracing */, false /* wasCancelled */);
-        flingSettings(0 /* velocity */, FLING_EXPAND);
-        // When expanding with a panel, there's no meaningful touch point to correspond to. Set the
-        // origin to somewhere above the screen. This is used for animations.
-        int x = mQsFrame.getWidth() / 2;
-        mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, x, -getHeight());
-        if (mAccessibilityManager.isEnabled()) {
-            mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
-        }
-    }
-
     public void expandWithoutQs() {
         if (isQsExpanded()) {
             flingSettings(0 /* velocity */, FLING_COLLAPSE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 716e8c7..f13334e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1110,6 +1110,12 @@
     }
 
     private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
+        Trace.beginSection("StatusBar#onFoldedStateChanged");
+        onFoldedStateChangedInternal(isFolded, willGoToSleep);
+        Trace.endSection();
+    }
+
+    private void onFoldedStateChangedInternal(boolean isFolded, boolean willGoToSleep) {
         // Folded state changes are followed by a screen off event.
         // By default turning off the screen also closes the shade.
         // We want to make sure that the shade status is kept after
@@ -3672,6 +3678,7 @@
 
         @Override
         public void onScreenTurnedOff() {
+            Trace.beginSection("StatusBar#onScreenTurnedOff");
             mFalsingCollector.onScreenOff();
             mScrimController.onScreenTurnedOff();
             if (mCloseQsBeforeScreenOff) {
@@ -3679,6 +3686,7 @@
                 mCloseQsBeforeScreenOff = false;
             }
             updateIsKeyguard();
+            Trace.endSection();
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index cf9a5db..4081962 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -235,11 +235,6 @@
         // Settings are not available in setup
         if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
 
-
-        QSPanelController qsPanelController = mStatusBar.getQSPanelController();
-        if (subPanel != null && qsPanelController != null) {
-            qsPanelController.openDetails(subPanel);
-        }
         mNotificationPanelViewController.expandWithQs();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 88a7dc7..c5d3937 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -27,7 +27,6 @@
 import android.view.ViewGroup;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
@@ -74,17 +73,19 @@
             Context context,
             CommandQueue commandQueue,
             DemoModeController demoModeController,
+            ConfigurationController configurationController,
+            TunerService tunerService,
             DumpManager dumpManager) {
         super(context.getResources().getStringArray(
                 com.android.internal.R.array.config_statusBarIcons));
-        Dependency.get(ConfigurationController.class).addCallback(this);
+        configurationController.addCallback(this);
 
         mContext = context;
 
         loadDimens();
 
         commandQueue.addCallback(this);
-        Dependency.get(TunerService.class).addTunable(this, ICON_HIDE_LIST);
+        tunerService.addTunable(this, ICON_HIDE_LIST);
         demoModeController.addCallback(this);
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
deleted file mode 100644
index 79d72b3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.dagger;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.row.RowContentBindStage;
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * This module provides instances needed to construct {@link StatusBar}. These are moved to this
- * separate from {@link StatusBarPhoneModule} module so that components that wish to build their own
- * version of StatusBar can include just dependencies, without injecting StatusBar itself.
- */
-@Module
-public interface StatusBarPhoneDependenciesModule {
-
-    /** */
-    @SysUISingleton
-    @Provides
-    static NotificationGroupAlertTransferHelper provideNotificationGroupAlertTransferHelper(
-            RowContentBindStage bindStage) {
-        return new NotificationGroupAlertTransferHelper(bindStage);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index d3ff4a7..83bdd1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -129,7 +129,7 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module(includes = {StatusBarPhoneDependenciesModule.class})
+@Module
 public interface StatusBarPhoneModule {
     /**
      * Provides our instance of StatusBar which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index e2dc905..d5f5362 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -21,6 +21,7 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
@@ -32,6 +33,8 @@
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
+import java.util.Optional;
+
 import javax.inject.Named;
 
 import dagger.Binds;
@@ -44,6 +47,7 @@
 
     String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view";
     String OPERATOR_NAME_VIEW = "operator_name_view";
+    String OPERATOR_NAME_FRAME_VIEW = "operator_name_frame_view";
 
     /** */
     @Provides
@@ -80,6 +84,14 @@
     /** */
     @Provides
     @StatusBarFragmentScope
+    @Named(OPERATOR_NAME_FRAME_VIEW)
+    static Optional<View> provideOperatorFrameNameView(@RootView PhoneStatusBarView view) {
+        return Optional.ofNullable(view.findViewById(R.id.operator_name_frame));
+    }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
     static Clock provideClock(@RootView PhoneStatusBarView view) {
         return view.findViewById(R.id.clock);
     }
@@ -119,4 +131,11 @@
     ) {
         return new PhoneStatusBarTransitions(view, statusBarWindowController.getBackgroundView());
     }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
+    static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) {
+        return view.findViewById(R.id.heads_up_status_bar_view);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
index 2dbc19c..b0f7629 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
@@ -19,12 +19,19 @@
 import android.graphics.drawable.Drawable
 import android.os.UserManager
 
-import com.android.systemui.DejankUtils.whitelistIpcs
+import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
 
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+
 import javax.inject.Inject
 
 /**
@@ -34,8 +41,11 @@
 @SysUISingleton
 class StatusBarUserInfoTracker @Inject constructor(
     private val userInfoController: UserInfoController,
-    private val userManager: UserManager
-) : CallbackController<CurrentUserChipInfoUpdatedListener> {
+    private val userManager: UserManager,
+    private val dumpManager: DumpManager,
+    @Main private val mainExecutor: Executor,
+    @Background private val backgroundExecutor: Executor
+) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable {
     var currentUserName: String? = null
         private set
     var currentUserAvatar: Drawable? = null
@@ -53,7 +63,7 @@
     }
 
     init {
-        startListening()
+        dumpManager.registerDumpable(TAG, this)
     }
 
     override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) {
@@ -96,27 +106,33 @@
         userInfoController.removeCallback(userInfoChangedListener)
     }
 
-    private fun checkUserSwitcherEnabled() {
-        whitelistIpcs {
-            userSwitcherEnabled = userManager.isUserSwitcherEnabled
-        }
-    }
-
     /**
      * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has
      * changed
      */
     fun checkEnabled() {
-        val wasEnabled = userSwitcherEnabled
-        checkUserSwitcherEnabled()
+        backgroundExecutor.execute {
+            // Check on a background thread to avoid main thread Binder calls
+            val wasEnabled = userSwitcherEnabled
+            userSwitcherEnabled = userManager.isUserSwitcherEnabled
 
-        if (wasEnabled != userSwitcherEnabled) {
-            notifyListenersSettingChanged()
+            if (wasEnabled != userSwitcherEnabled) {
+                mainExecutor.execute {
+                    notifyListenersSettingChanged()
+                }
+            }
         }
     }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("  userSwitcherEnabled=$userSwitcherEnabled")
+        pw.println("  listening=$listening")
+    }
 }
 
 interface CurrentUserChipInfoUpdatedListener {
     fun onCurrentUserChipInfoUpdated()
     fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {}
 }
+
+private const val TAG = "StatusBarUserInfoTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
index a124753..909261f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
@@ -16,9 +16,15 @@
 
 package com.android.systemui.statusbar.phone.userswitcher
 
+import android.content.Intent
 import android.view.View
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
 
 import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.user.UserSwitcherActivity
 import com.android.systemui.util.ViewController
 
 import javax.inject.Inject
@@ -30,7 +36,9 @@
     view: StatusBarUserSwitcherContainer,
     private val tracker: StatusBarUserInfoTracker,
     private val featureController: StatusBarUserSwitcherFeatureController,
-    private val userSwitcherDialogController: UserSwitchDialogController
+    private val userSwitcherDialogController: UserSwitchDialogController,
+    private val featureFlags: FeatureFlags,
+    private val activityStarter: ActivityStarter
 ) : ViewController<StatusBarUserSwitcherContainer>(view),
         StatusBarUserSwitcherController {
     private val listener = object : CurrentUserChipInfoUpdatedListener {
@@ -52,8 +60,17 @@
     override fun onViewAttached() {
         tracker.addCallback(listener)
         featureController.addCallback(featureFlagListener)
-        mView.setOnClickListener {
-            userSwitcherDialogController.showDialog(it)
+        mView.setOnClickListener { view: View ->
+            if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+                val intent = Intent(context, UserSwitcherActivity::class.java)
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                activityStarter.startActivity(intent, true /* dismissShade */,
+                        ActivityLaunchAnimator.Controller.fromView(view, null),
+                        true /* showOverlockscreenwhenlocked */)
+            } else {
+                userSwitcherDialogController.showDialog(view)
+            }
         }
 
         updateEnabled()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 97d344a..562816f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -56,6 +56,7 @@
 import com.android.systemui.tuner.TunerService.Tunable;
 
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -314,8 +315,8 @@
     }
 
     @Override
-    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        mNonAdaptedColor = DarkIconDispatcher.getTint(area, this, tint);
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        mNonAdaptedColor = DarkIconDispatcher.getTint(areas, this, tint);
         setTextColor(mNonAdaptedColor);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index d903639..1d414745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -21,6 +21,7 @@
 
 import android.annotation.Nullable;
 import android.hardware.devicestate.DeviceStateManager;
+import android.os.Trace;
 import android.util.Log;
 
 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
@@ -117,11 +118,16 @@
 
     private void updateDeviceState(int state) {
         Log.v(TAG, "updateDeviceState [state=" + state + "]");
-        if (mDeviceState == state) {
-            return;
-        }
+        Trace.beginSection("updateDeviceState [state=" + state + "]");
+        try {
+            if (mDeviceState == state) {
+                return;
+            }
 
-        readPersistedSetting(state);
+            readPersistedSetting(state);
+        } finally {
+            Trace.endSection();
+        }
     }
 
     private void readPersistedSetting(int state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index b591545..7e2488f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -37,12 +37,9 @@
 import com.android.systemui.R;
 import com.android.systemui.communal.CommunalStateController;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.tiles.UserDetailView;
 import com.android.systemui.qs.user.UserSwitchDialogController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -51,13 +48,11 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.UserAvatarView;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 /**
  * Manages the user switch on the Keyguard that is used for opening the QS user panel.
@@ -81,11 +76,8 @@
     protected final SysuiStatusBarStateController mStatusBarStateController;
     private final ConfigurationController mConfigurationController;
     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
-    private final KeyguardUserDetailAdapter mUserDetailAdapter;
-    private final FeatureFlags mFeatureFlags;
     private final UserSwitchDialogController mUserSwitchDialogController;
     private final UiEventLogger mUiEventLogger;
-    private NotificationPanelViewController mNotificationPanelViewController;
     private UserAvatarView mUserAvatarView;
     UserSwitcherController.UserRecord mCurrentUser;
 
@@ -133,9 +125,7 @@
             ConfigurationController configurationController,
             SysuiStatusBarStateController statusBarStateController,
             DozeParameters dozeParameters,
-            Provider<UserDetailView.Adapter> userDetailViewAdapterProvider,
             ScreenOffAnimationController screenOffAnimationController,
-            FeatureFlags featureFlags,
             UserSwitchDialogController userSwitchDialogController,
             UiEventLogger uiEventLogger) {
         super(view);
@@ -152,8 +142,6 @@
                 keyguardStateController, dozeParameters,
                 screenOffAnimationController,  /* animateYPos= */ false,
                 /* visibleOnCommunal= */ false);
-        mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
-        mFeatureFlags = featureFlags;
         mUserSwitchDialogController = userSwitchDialogController;
         mUiEventLogger = uiEventLogger;
     }
@@ -182,11 +170,7 @@
             mUiEventLogger.log(
                     LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
 
-            if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
-                mUserSwitchDialogController.showDialog(mView);
-            } else {
-                openQsUserPanel();
-            }
+            mUserSwitchDialogController.showDialog(mView);
         });
 
         mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@@ -331,40 +315,4 @@
     private boolean isListAnimating() {
         return mKeyguardVisibilityHelper.isVisibilityAnimating();
     }
-
-    private void openQsUserPanel() {
-        mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter);
-    }
-
-    public void setNotificationPanelViewController(
-            NotificationPanelViewController notificationPanelViewController) {
-        mNotificationPanelViewController = notificationPanelViewController;
-    }
-
-    class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter {
-        KeyguardUserDetailAdapter(Context context,
-                Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) {
-            super(context, userDetailViewAdapterProvider);
-        }
-
-        @Override
-        public boolean shouldAnimate() {
-            return false;
-        }
-
-        @Override
-        public int getDoneText() {
-            return R.string.quick_settings_close_user_panel;
-        }
-
-        @Override
-        public boolean onDoneButtonClicked() {
-            if (mNotificationPanelViewController != null) {
-                mNotificationPanelViewController.animateCloseQs(true /* animateAway */);
-                return true;
-            } else {
-                return false;
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 3ece240..e416ed1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -46,11 +46,11 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.telephony.TelephonyCallback;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowManagerGlobal;
 import android.widget.BaseAdapter;
 
@@ -59,7 +59,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.systemui.Dumpable;
@@ -76,9 +75,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.QSUserSwitcherEvent;
-import com.android.systemui.qs.tiles.UserDetailView;
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -95,7 +92,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 /**
  * Keeps a list of all users on the device for user switching.
@@ -152,13 +148,13 @@
     private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
     private final UiEventLogger mUiEventLogger;
     private final IActivityManager mActivityManager;
-    public final DetailAdapter mUserDetailAdapter;
     private final Executor mBgExecutor;
     private final boolean mGuestUserAutoCreated;
     private final AtomicBoolean mGuestIsResetting;
     private final AtomicBoolean mGuestCreationScheduled;
     private FalsingManager mFalsingManager;
     private View mView;
+    private String mCreateSupervisedUserPackage;
 
     @Inject
     public UserSwitcherController(Context context,
@@ -175,7 +171,6 @@
             FalsingManager falsingManager,
             TelephonyListenerManager telephonyListenerManager,
             IActivityTaskManager activityTaskManager,
-            UserDetailAdapter userDetailAdapter,
             SecureSettings secureSettings,
             @Background Executor bgExecutor,
             InteractionJankMonitor interactionJankMonitor,
@@ -194,7 +189,6 @@
         mLatencyTracker = latencyTracker;
         mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(
                 this, mUserTracker, mUiEventLogger, secureSettings);
-        mUserDetailAdapter = userDetailAdapter;
         mBgExecutor = bgExecutor;
         if (!UserManager.isGuestUserEphemeral()) {
             mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
@@ -255,6 +249,9 @@
         keyguardStateController.addCallback(mCallback);
         listenForCallState();
 
+        mCreateSupervisedUserPackage = mContext.getString(
+                com.android.internal.R.string.config_supervisedUserCreationPackage);
+
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
         refreshUsers(UserHandle.USER_NULL);
@@ -307,14 +304,10 @@
                 // User 0
                 boolean canSwitchUsers = mUserManager.getUserSwitchability(
                         UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
-                UserInfo currentUserInfo = null;
                 UserRecord guestRecord = null;
 
                 for (UserInfo info : infos) {
                     boolean isCurrent = currentId == info.id;
-                    if (isCurrent) {
-                        currentUserInfo = info;
-                    }
                     boolean switchToEnabled = canSwitchUsers || isCurrent;
                     if (info.isEnabled()) {
                         if (info.isGuest()) {
@@ -322,7 +315,8 @@
                             // the icon shouldn't be enabled even if the user is current
                             guestRecord = new UserRecord(info, null /* picture */,
                                     true /* isGuest */, isCurrent, false /* isAddUser */,
-                                    false /* isRestricted */, canSwitchUsers);
+                                    false /* isRestricted */, canSwitchUsers,
+                                    false /* isAddSupervisedUser */);
                         } else if (info.supportsSwitchToByUser()) {
                             Bitmap picture = bitmaps.get(info.id);
                             if (picture == null) {
@@ -337,7 +331,7 @@
                             }
                             records.add(new UserRecord(info, picture, false /* isGuest */,
                                     isCurrent, false /* isAddUser */, false /* isRestricted */,
-                                    switchToEnabled));
+                                    switchToEnabled, false /* isAddSupervisedUser */));
                         }
                     }
                 }
@@ -345,19 +339,6 @@
                     Prefs.putBoolean(mContext, Key.SEEN_MULTI_USER, true);
                 }
 
-                boolean systemCanCreateUsers = !mUserManager.hasBaseUserRestriction(
-                                UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
-                boolean currentUserCanCreateUsers = currentUserInfo != null
-                        && (currentUserInfo.isAdmin()
-                                || currentUserInfo.id == UserHandle.USER_SYSTEM)
-                        && systemCanCreateUsers;
-                boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked;
-                boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
-                        && guestRecord == null;
-                boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
-                        && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
-                boolean createIsRestricted = !addUsersWhenLocked;
-
                 if (guestRecord == null) {
                     if (mGuestUserAutoCreated) {
                         // If mGuestIsResetting=true, the switch should be disabled since
@@ -368,13 +349,14 @@
                         guestRecord = new UserRecord(null /* info */, null /* picture */,
                                 true /* isGuest */, false /* isCurrent */,
                                 false /* isAddUser */, false /* isRestricted */,
-                                isSwitchToGuestEnabled);
+                                isSwitchToGuestEnabled, false /* isAddSupervisedUser */);
                         checkIfAddUserDisallowedByAdminOnly(guestRecord);
                         records.add(guestRecord);
-                    } else if (canCreateGuest) {
+                    } else if (canCreateGuest(guestRecord != null)) {
                         guestRecord = new UserRecord(null /* info */, null /* picture */,
                                 true /* isGuest */, false /* isCurrent */,
-                                false /* isAddUser */, createIsRestricted, canSwitchUsers);
+                                false /* isAddUser */, createIsRestricted(), canSwitchUsers,
+                                false /* isAddSupervisedUser */);
                         checkIfAddUserDisallowedByAdminOnly(guestRecord);
                         records.add(guestRecord);
                     }
@@ -382,10 +364,19 @@
                     records.add(guestRecord);
                 }
 
-                if (canCreateUser) {
+                if (canCreateUser()) {
                     UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
                             false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
-                            createIsRestricted, canSwitchUsers);
+                            createIsRestricted(), canSwitchUsers,
+                            false /* isAddSupervisedUser */);
+                    checkIfAddUserDisallowedByAdminOnly(addUserRecord);
+                    records.add(addUserRecord);
+                }
+
+                if (canCreateSupervisedUser()) {
+                    UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
+                            false /* isGuest */, false /* isCurrent */, false /* isAddUser */,
+                            createIsRestricted(), canSwitchUsers, true /* isAddSupervisedUser */);
                     checkIfAddUserDisallowedByAdminOnly(addUserRecord);
                     records.add(addUserRecord);
                 }
@@ -403,6 +394,40 @@
         }.execute((SparseArray) bitmaps);
     }
 
+    boolean systemCanCreateUsers() {
+        return !mUserManager.hasBaseUserRestriction(
+                UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
+    }
+
+    boolean currentUserCanCreateUsers() {
+        UserInfo currentUser = mUserTracker.getUserInfo();
+        return currentUser != null
+                && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
+                && systemCanCreateUsers();
+    }
+
+    boolean anyoneCanCreateUsers() {
+        return systemCanCreateUsers() && mAddUsersFromLockScreen;
+    }
+
+    boolean canCreateGuest(boolean hasExistingGuest) {
+        return (currentUserCanCreateUsers() || anyoneCanCreateUsers())
+                && !hasExistingGuest;
+    }
+
+    boolean canCreateUser() {
+        return (currentUserCanCreateUsers() || anyoneCanCreateUsers())
+                && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
+    }
+
+    boolean createIsRestricted() {
+        return !mAddUsersFromLockScreen;
+    }
+
+    boolean canCreateSupervisedUser() {
+        return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
+    }
+
     private void pauseRefreshUsers() {
         if (!mPauseRefreshUsers) {
             mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
@@ -485,6 +510,9 @@
         } else if (record.isAddUser) {
             showAddUserDialog(dialogShower);
             return;
+        } else if (record.isAddSupervisedUser) {
+            startSupervisedUserActivity();
+            return;
         } else {
             id = record.info.id;
         }
@@ -561,6 +589,22 @@
         }
     }
 
+    private void startSupervisedUserActivity() {
+        final Intent intent = new Intent()
+                .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+                .setPackage(mCreateSupervisedUserPackage)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // TODO(b/209659998): [to-be-removed] fallback activity for supervised user creation.
+        if (mContext.getPackageManager().resolveActivity(intent, 0) == null) {
+            intent.setPackage(null)
+                    .setClassName("com.android.settings",
+                        "com.android.settings.users.AddSupervisedUserActivity");
+        }
+
+        mContext.startActivity(intent);
+    }
+
     private void listenForCallState() {
         mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
     }
@@ -941,6 +985,8 @@
                 }
             } else if (item.isAddUser) {
                 return context.getString(R.string.user_add_user);
+            } else if (item.isAddSupervisedUser) {
+                return context.getString(R.string.add_user_supervised);
             } else {
                 return item.info.name;
             }
@@ -955,9 +1001,11 @@
         protected static Drawable getIconDrawable(Context context, UserRecord item) {
             int iconRes;
             if (item.isAddUser) {
-                iconRes = R.drawable.ic_add_circle;
+                iconRes = R.drawable.ic_account_circle;
             } else if (item.isGuest) {
-                iconRes = R.drawable.ic_avatar_guest_user;
+                iconRes = R.drawable.ic_account_circle_filled;
+            } else if (item.isAddSupervisedUser) {
+                iconRes = R.drawable.ic_add_supervised_user;
             } else {
                 iconRes = R.drawable.ic_avatar_user;
             }
@@ -1000,6 +1048,7 @@
         public final boolean isGuest;
         public final boolean isCurrent;
         public final boolean isAddUser;
+        public final boolean isAddSupervisedUser;
         /** If true, the record is only visible to the owner and only when unlocked. */
         public final boolean isRestricted;
         public boolean isDisabledByAdmin;
@@ -1007,7 +1056,8 @@
         public boolean isSwitchToEnabled;
 
         public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
-                boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled) {
+                boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled,
+                boolean isAddSupervisedUser) {
             this.info = info;
             this.picture = picture;
             this.isGuest = isGuest;
@@ -1015,11 +1065,12 @@
             this.isAddUser = isAddUser;
             this.isRestricted = isRestricted;
             this.isSwitchToEnabled = isSwitchToEnabled;
+            this.isAddSupervisedUser = isAddSupervisedUser;
         }
 
         public UserRecord copyWithIsCurrent(boolean _isCurrent) {
             return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted,
-                    isSwitchToEnabled);
+                    isSwitchToEnabled, isAddSupervisedUser);
         }
 
         public int resolveId() {
@@ -1043,6 +1094,7 @@
             }
             if (isGuest) sb.append(" <isGuest>");
             if (isAddUser) sb.append(" <isAddUser>");
+            if (isAddSupervisedUser) sb.append(" <isAddSupervisedUser>");
             if (isCurrent) sb.append(" <isCurrent>");
             if (picture != null) sb.append(" <hasPicture>");
             if (isRestricted) sb.append(" <isRestricted>");
@@ -1058,77 +1110,6 @@
         }
     }
 
-    public static class UserDetailAdapter implements DetailAdapter {
-        private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS);
-
-        private final Context mContext;
-        private final Provider<UserDetailView.Adapter> mUserDetailViewAdapterProvider;
-
-        @Inject
-        UserDetailAdapter(Context context,
-                Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) {
-            mContext = context;
-            mUserDetailViewAdapterProvider = userDetailViewAdapterProvider;
-        }
-
-        @Override
-        public CharSequence getTitle() {
-            return mContext.getString(R.string.quick_settings_user_title);
-        }
-
-        @Override
-        public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            UserDetailView v;
-            if (!(convertView instanceof UserDetailView)) {
-                v = UserDetailView.inflate(context, parent, false);
-                v.setAdapter(mUserDetailViewAdapterProvider.get());
-            } else {
-                v = (UserDetailView) convertView;
-            }
-            v.refreshAdapter();
-            return v;
-        }
-
-        @Override
-        public Intent getSettingsIntent() {
-            return USER_SETTINGS_INTENT;
-        }
-
-        @Override
-        public int getSettingsText() {
-            return R.string.quick_settings_more_user_settings;
-        }
-
-        @Override
-        public Boolean getToggleState() {
-            return null;
-        }
-
-        @Override
-        public void setToggleState(boolean state) {
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return MetricsEvent.QS_USERDETAIL;
-        }
-
-        @Override
-        public UiEventLogger.UiEventEnum openDetailEvent() {
-            return QSUserSwitcherEvent.QS_USER_DETAIL_OPEN;
-        }
-
-        @Override
-        public UiEventLogger.UiEventEnum closeDetailEvent() {
-            return QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE;
-        }
-
-        @Override
-        public UiEventLogger.UiEventEnum moreSettingsEvent() {
-            return QSUserSwitcherEvent.QS_USER_MORE_SETTINGS;
-        }
-    };
-
     private final KeyguardStateController.Callback mCallback =
             new KeyguardStateController.Callback() {
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
index 52b58d4..71355bb 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
@@ -23,6 +23,7 @@
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TunerSwitch);
         mDefault = a.getBoolean(R.styleable.TunerSwitch_defValue, false);
         mAction = a.getInt(R.styleable.TunerSwitch_metricsAction, -1);
+        a.recycle();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
deleted file mode 100644
index 4d95969..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 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.tuner;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.provider.Settings.Global;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Checkable;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.volume.ZenModePanel;
-import com.android.systemui.volume.ZenModePanel.Callback;
-
-public class TunerZenModePanel extends LinearLayout implements OnClickListener {
-    private static final String TAG = "TunerZenModePanel";
-
-    private Callback mCallback;
-    private ZenModePanel mZenModePanel;
-    private View mHeaderSwitch;
-    private int mZenMode;
-    private ZenModeController mController;
-    private View mButtons;
-    private View mMoreSettings;
-    private View mDone;
-    private OnClickListener mDoneListener;
-    private boolean mEditing;
-
-    public TunerZenModePanel(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public void init(ZenModeController zenModeController) {
-        mController = zenModeController;
-        mHeaderSwitch = findViewById(R.id.tuner_zen_switch);
-        mHeaderSwitch.setVisibility(View.VISIBLE);
-        mHeaderSwitch.setOnClickListener(this);
-        ((TextView) mHeaderSwitch.findViewById(android.R.id.title)).setText(
-                R.string.quick_settings_dnd_label);
-        mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel);
-        mZenModePanel.init(zenModeController);
-        mButtons = findViewById(R.id.tuner_zen_buttons);
-        mMoreSettings = mButtons.findViewById(android.R.id.button2);
-        mMoreSettings.setOnClickListener(this);
-        ((TextView) mMoreSettings).setText(R.string.quick_settings_more_settings);
-        mDone = mButtons.findViewById(android.R.id.button1);
-        mDone.setOnClickListener(this);
-        ((TextView) mDone).setText(R.string.quick_settings_done);
-        // Hide the resizing space because it causes issues in the volume panel.
-        ViewGroup detail_header = findViewById(R.id.tuner_zen_switch);
-        detail_header.getChildAt(0).setVisibility(View.GONE);
-        // No background so it can blend with volume panel.
-        findViewById(R.id.edit_container).setBackground(null);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mEditing = false;
-    }
-
-    public void setCallback(Callback zenPanelCallback) {
-        mCallback = zenPanelCallback;
-        mZenModePanel.setCallback(zenPanelCallback);
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (v == mHeaderSwitch) {
-            mEditing = true;
-            if (mZenMode == Global.ZEN_MODE_OFF) {
-                mZenMode = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
-                        Global.ZEN_MODE_ALARMS);
-                mController.setZen(mZenMode, null, TAG);
-                postUpdatePanel();
-            } else {
-                mZenMode = Global.ZEN_MODE_OFF;
-                mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
-                postUpdatePanel();
-            }
-        } else if (v == mMoreSettings) {
-            Intent intent = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            getContext().startActivity(intent);
-        } else if (v == mDone) {
-            mEditing = false;
-            setVisibility(View.GONE);
-            mDoneListener.onClick(v);
-        }
-    }
-
-    public boolean isEditing() {
-        return mEditing;
-    }
-
-    public void setZenState(int zenMode) {
-        mZenMode = zenMode;
-        postUpdatePanel();
-    }
-
-    private void postUpdatePanel() {
-        // The complicated structure from reusing the same ZenPanel has resulted in some
-        // unstableness/flickering from callbacks coming in quickly. To solve this just
-        // post the UI updates a little bit.
-        removeCallbacks(mUpdate);
-        postDelayed(mUpdate, 40);
-    }
-
-    public void setDoneListener(OnClickListener onClickListener) {
-        mDoneListener = onClickListener;
-    }
-
-    private void updatePanel() {
-        boolean zenOn = mZenMode != Global.ZEN_MODE_OFF;
-        ((Checkable) mHeaderSwitch.findViewById(android.R.id.toggle)).setChecked(zenOn);
-        mZenModePanel.setVisibility(zenOn ? View.VISIBLE : View.GONE);
-        mButtons.setVisibility(zenOn ? View.VISIBLE : View.GONE);
-    }
-
-    private final Runnable mUpdate = new Runnable() {
-        @Override
-        public void run() {
-            updatePanel();
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
new file mode 100644
index 0000000..ad8dc82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.tv
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.SliceBroadcastRelayHandler
+import com.android.systemui.accessibility.WindowMagnification
+import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.keyboard.KeyboardUI
+import com.android.systemui.media.RingtonePlayer
+import com.android.systemui.media.systemsounds.HomeSoundEffectController
+import com.android.systemui.power.PowerUI
+import com.android.systemui.privacy.television.TvOngoingPrivacyChip
+import com.android.systemui.shortcut.ShortcutKeyDispatcher
+import com.android.systemui.statusbar.notification.InstantAppNotifier
+import com.android.systemui.statusbar.tv.TvStatusBar
+import com.android.systemui.statusbar.tv.VpnStatusObserver
+import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler
+import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel
+import com.android.systemui.toast.ToastUI
+import com.android.systemui.usb.StorageNotification
+import com.android.systemui.util.NotificationChannels
+import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wmshell.WMShell
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+/**
+ * Collection of {@link CoreStartable}s that should be run on TV.
+ */
+@Module
+abstract class TVSystemUICoreStartableModule {
+    /** Inject into GlobalActionsComponent.  */
+    @Binds
+    @IntoMap
+    @ClassKey(GlobalActionsComponent::class)
+    abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable
+
+    /** Inject into HomeSoundEffectController.  */
+    @Binds
+    @IntoMap
+    @ClassKey(HomeSoundEffectController::class)
+    abstract fun bindHomeSoundEffectController(sysui: HomeSoundEffectController): CoreStartable
+
+    /** Inject into InstantAppNotifier.  */
+    @Binds
+    @IntoMap
+    @ClassKey(InstantAppNotifier::class)
+    abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable
+
+    /** Inject into KeyboardUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyboardUI::class)
+    abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
+
+    /** Inject into NotificationChannels.  */
+    @Binds
+    @IntoMap
+    @ClassKey(NotificationChannels::class)
+    @PerUser
+    abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
+
+    /** Inject into PowerUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(PowerUI::class)
+    abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
+
+    /** Inject into RingtonePlayer.  */
+    @Binds
+    @IntoMap
+    @ClassKey(RingtonePlayer::class)
+    abstract fun bind(sysui: RingtonePlayer): CoreStartable
+
+    /** Inject into ShortcutKeyDispatcher.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ShortcutKeyDispatcher::class)
+    abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable
+
+    /** Inject into SliceBroadcastRelayHandler.  */
+    @Binds
+    @IntoMap
+    @ClassKey(SliceBroadcastRelayHandler::class)
+    abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable
+
+    /** Inject into StorageNotification.  */
+    @Binds
+    @IntoMap
+    @ClassKey(StorageNotification::class)
+    abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
+
+    /** Inject into ToastUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ToastUI::class)
+    abstract fun bindToastUI(service: ToastUI): CoreStartable
+
+    /** Inject into TvNotificationHandler.  */
+    @Binds
+    @IntoMap
+    @ClassKey(TvNotificationHandler::class)
+    abstract fun bindTvNotificationHandler(sysui: TvNotificationHandler): CoreStartable
+
+    /** Inject into TvNotificationPanel.  */
+    @Binds
+    @IntoMap
+    @ClassKey(TvNotificationPanel::class)
+    abstract fun bindTvNotificationPanel(sysui: TvNotificationPanel): CoreStartable
+
+    /** Inject into TvOngoingPrivacyChip.  */
+    @Binds
+    @IntoMap
+    @ClassKey(TvOngoingPrivacyChip::class)
+    abstract fun bindTvOngoingPrivacyChip(sysui: TvOngoingPrivacyChip): CoreStartable
+
+    /** Inject into TvStatusBar.  */
+    @Binds
+    @IntoMap
+    @ClassKey(TvStatusBar::class)
+    abstract fun bindTvStatusBar(sysui: TvStatusBar): CoreStartable
+
+    /** Inject into VolumeUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(VolumeUI::class)
+    abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
+
+    /** Inject into VpnStatusObserver.  */
+    @Binds
+    @IntoMap
+    @ClassKey(VpnStatusObserver::class)
+    abstract fun bindVpnStatusObserver(sysui: VpnStatusObserver): CoreStartable
+
+    /** Inject into WindowMagnification.  */
+    @Binds
+    @IntoMap
+    @ClassKey(WindowMagnification::class)
+    abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable
+
+    /** Inject into WMShell.  */
+    @Binds
+    @IntoMap
+    @ClassKey(WMShell::class)
+    abstract fun bindWMShell(sysui: WMShell): CoreStartable
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index bef05eb..6fdce1a 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -34,6 +34,7 @@
         DependencyProvider.class,
         SystemUIBinder.class,
         SystemUIModule.class,
+        TVSystemUICoreStartableModule.class,
         TvSystemUIModule.class,
         TvSystemUIBinder.class})
 public interface TvSysUIComponent extends SysUIComponent {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index d0fb91c..23f37ec 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -16,28 +16,13 @@
 
 package com.android.systemui.tv;
 
-import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.GlobalRootComponent;
-import com.android.systemui.statusbar.tv.VpnStatusObserver;
-import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
 
 import dagger.Binds;
 import dagger.Module;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
 
 @Module
 interface TvSystemUIBinder {
     @Binds
     GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
-
-    @Binds
-    @IntoMap
-    @ClassKey(TvNotificationHandler.class)
-    CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui);
-
-    @Binds
-    @IntoMap
-    @ClassKey(VpnStatusObserver.class)
-    CoreStartable bindVpnStatusObserver(VpnStatusObserver systemui);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index cf361ec..345fc99f 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -46,10 +46,15 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.util.NotificationChannels;
 
 import java.util.List;
 
+import javax.inject.Inject;
+
+/** */
+@SysUISingleton
 public class StorageNotification extends CoreStartable {
     private static final String TAG = "StorageNotification";
 
@@ -61,6 +66,7 @@
     private NotificationManager mNotificationManager;
     private StorageManager mStorageManager;
 
+    @Inject
     public StorageNotification(Context context) {
         super(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index d6a8ab2..41da44a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -27,11 +27,10 @@
 import android.graphics.drawable.LayerDrawable
 import android.os.Bundle
 import android.os.UserManager
+import android.provider.Settings
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowInsets.Type
 import android.widget.AdapterView
 import android.widget.ArrayAdapter
 import android.widget.ImageView
@@ -71,8 +70,18 @@
     private lateinit var broadcastReceiver: BroadcastReceiver
     private var popupMenu: UserSwitcherPopupMenu? = null
     private lateinit var addButton: View
-    private var addUserItem: UserRecord? = null
-    private var addGuestItem: UserRecord? = null
+    private var addUserRecords = mutableListOf<UserRecord>()
+    // When the add users options become available, insert another option to manage users
+    private val manageUserRecord = UserRecord(
+        null /* info */,
+        null /* picture */,
+        false /* isGuest */,
+        false /* isCurrent */,
+        false /* isAddUser */,
+        false /* isRestricted */,
+        false /* isSwitchToEnabled */,
+        false /* isAddSupervisedUser */
+    )
 
     private val adapter = object : BaseUserAdapter(userSwitcherController) {
         override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
@@ -104,7 +113,18 @@
             return view
         }
 
+        override fun getName(context: Context, item: UserRecord): String {
+            return if (item == manageUserRecord) {
+                getString(R.string.manage_users)
+            } else {
+                super.getName(context, item)
+            }
+        }
+
         fun findUserIcon(item: UserRecord): Drawable {
+            if (item == manageUserRecord) {
+                return getDrawable(R.drawable.ic_manage_users)
+            }
             if (item.info == null) {
                 return getIconDrawable(this@UserSwitcherActivity, item)
             }
@@ -169,20 +189,11 @@
         super.onCreate(savedInstanceState)
 
         setContentView(R.layout.user_switcher_fullscreen)
+        window.decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+            or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+            or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
 
-        parent = requireViewById<ViewGroup>(R.id.user_switcher_root).apply {
-            setOnApplyWindowInsetsListener {
-                v: View, insets: WindowInsets ->
-                    v.apply {
-                        val l = getPaddingLeft()
-                        val t = getPaddingTop()
-                        val r = getPaddingRight()
-                        setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
-                    }
-
-                WindowInsets.CONSUMED
-            }
-        }
+        parent = requireViewById<ViewGroup>(R.id.user_switcher_root)
 
         requireViewById<View>(R.id.cancel).apply {
             setOnClickListener {
@@ -203,15 +214,19 @@
 
     private fun showPopupMenu() {
         val items = mutableListOf<UserRecord>()
-        addUserItem?.let { items.add(it) }
-        addGuestItem?.let { items.add(it) }
+        addUserRecords.forEach { items.add(it) }
 
         var popupMenuAdapter = ItemAdapter(
             this,
             R.layout.user_switcher_fullscreen_popup_item,
             layoutInflater,
             { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) },
-            { item: UserRecord -> adapter.findUserIcon(item) }
+            { item: UserRecord -> adapter.findUserIcon(item).mutate().apply {
+                setTint(resources.getColor(
+                    R.color.user_switcher_fullscreen_popup_item_tint,
+                    getTheme()
+                ))
+            } }
         )
         popupMenuAdapter.addAll(items)
 
@@ -225,10 +240,17 @@
                     }
                     // -1 for the header
                     val item = popupMenuAdapter.getItem(pos - 1)
-                    adapter.onUserListItemClicked(item)
+                    if (item == manageUserRecord) {
+                        val i = Intent().setAction(Settings.ACTION_USER_SETTINGS)
+                        [email protected](i)
+                    } else {
+                        adapter.onUserListItemClicked(item)
+                    }
 
                     dismiss()
                     popupMenu = null
+
+                    [email protected]()
             }
 
             show()
@@ -245,14 +267,15 @@
             }
         }
         parent.removeViews(start, count)
+        addUserRecords.clear()
 
         val flow = requireViewById<Flow>(R.id.flow)
         for (i in 0 until adapter.getCount()) {
             val item = adapter.getItem(i)
-            if (item.isAddUser) {
-                addUserItem = item
-            } else if (item.isGuest && item.info == null) {
-                addGuestItem = item
+            if (item.isAddUser ||
+                item.isAddSupervisedUser ||
+                item.isGuest && item.info == null) {
+                addUserRecords.add(item)
             } else {
                 val userView = adapter.getView(i, null, parent)
                 userView.setId(View.generateViewId())
@@ -273,7 +296,8 @@
             }
         }
 
-        if (addUserItem != null || addGuestItem != null) {
+        if (!addUserRecords.isEmpty()) {
+            addUserRecords.add(manageUserRecord)
             addButton.visibility = View.VISIBLE
         } else {
             addButton.visibility = View.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index 8963547..754a934 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -42,7 +42,7 @@
         setBackgroundDrawable(
             res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())
         )
-        setModal(true)
+        setModal(false)
         setOverlapAnchor(true)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index ce7e4cf..76dfcb1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -29,6 +29,9 @@
 
 import java.util.Arrays;
 
+import javax.inject.Inject;
+
+// NOT Singleton. Started per-user.
 public class NotificationChannels extends CoreStartable {
     public static String ALERTS      = "ALR";
     public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
@@ -38,6 +41,7 @@
     public static String TVPIP       = TvPipNotificationController.NOTIFICATION_CHANNEL; // "TVPIP"
     public static String HINTS       = "HNT";
 
+    @Inject
     public NotificationChannels(Context context) {
         super(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index 0bbf56c..db35437e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -36,6 +36,7 @@
     private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
     private boolean mIsConditionMet = false;
     private boolean mStarted = false;
+    private boolean mOverriding = false;
 
     /**
      * Starts monitoring the condition.
@@ -48,6 +49,21 @@
     protected abstract void stop();
 
     /**
+     * Sets whether this condition's value overrides others in determining the overall state.
+     */
+    public void setOverriding(boolean overriding) {
+        mOverriding = overriding;
+        updateCondition(mIsConditionMet);
+    }
+
+    /**
+     * Returns whether the current condition overrides
+     */
+    public boolean isOverridingCondition() {
+        return mOverriding;
+    }
+
+    /**
      * Registers a callback to receive updates once started. This should be called before
      * {@link #start()}. Also triggers the callback immediately if already started.
      */
@@ -57,7 +73,7 @@
         mCallbacks.add(new WeakReference<>(callback));
 
         if (mStarted) {
-            callback.onConditionChanged(this, mIsConditionMet);
+            callback.onConditionChanged(this);
             return;
         }
 
@@ -107,11 +123,15 @@
             if (cb == null) {
                 iterator.remove();
             } else {
-                cb.onConditionChanged(this, mIsConditionMet);
+                cb.onConditionChanged(this);
             }
         }
     }
 
+    public boolean isConditionMet() {
+        return mIsConditionMet;
+    }
+
     private boolean shouldLog() {
         return Log.isLoggable(mTag, Log.DEBUG);
     }
@@ -124,8 +144,7 @@
          * Called when the fulfillment of the condition changes.
          *
          * @param condition The condition in question.
-         * @param isConditionMet True if the condition has been fulfilled. False otherwise.
          */
-        void onConditionChanged(Condition condition, boolean isConditionMet);
+        void onConditionChanged(Condition condition);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 8b6e982..7f3d54d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -18,15 +18,17 @@
 
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -41,9 +43,7 @@
 
     // Set of all conditions that need to be monitored.
     private final Set<Condition> mConditions;
-
-    // Map of values of each condition.
-    private final HashMap<Condition, Boolean> mConditionsMap = new HashMap<>();
+    private final Executor mExecutor;
 
     // Whether all conditions have been met.
     private boolean mAllConditionsMet = false;
@@ -52,10 +52,43 @@
     private boolean mHaveConditionsStarted = false;
 
     // Callback for when each condition has been updated.
-    private final Condition.Callback mConditionCallback = (condition, isConditionMet) -> {
-        mConditionsMap.put(condition, isConditionMet);
+    private final Condition.Callback mConditionCallback = new Condition.Callback() {
+        @Override
+        public void onConditionChanged(Condition condition) {
+            mExecutor.execute(() -> updateConditionMetState());
+        }
+    };
 
-        final boolean newAllConditionsMet = !mConditionsMap.containsValue(false);
+    @Inject
+    public Monitor(Executor executor, Set<Condition> conditions, Set<Callback> callbacks) {
+        mConditions = new HashSet<>();
+        mExecutor = executor;
+
+        if (conditions != null) {
+            mConditions.addAll(conditions);
+        }
+
+        if (callbacks == null) {
+            return;
+        }
+
+        for (Callback callback : callbacks) {
+            addCallbackLocked(callback);
+        }
+    }
+
+    private void updateConditionMetState() {
+        // Overriding conditions do not override each other
+        final Collection<Condition> overridingConditions = mConditions.stream()
+                .filter(Condition::isOverridingCondition).collect(Collectors.toSet());
+
+        final Collection<Condition> targetCollection = overridingConditions.isEmpty()
+                ? mConditions : overridingConditions;
+
+        final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
+                .stream()
+                .map(Condition::isConditionMet)
+                .allMatch(conditionMet -> conditionMet);
 
         if (newAllConditionsMet == mAllConditionsMet) {
             return;
@@ -74,32 +107,44 @@
                 callback.onConditionsChanged(mAllConditionsMet);
             }
         }
-    };
-
-    @Inject
-    public Monitor(Set<Condition> conditions, Set<Callback> callbacks) {
-        mConditions = conditions;
-
-        // If there is no condition, give green pass.
-        if (mConditions.isEmpty()) {
-            mAllConditionsMet = true;
-            return;
-        }
-
-        // Initializes the conditions map and registers a callback for each condition.
-        mConditions.forEach((condition -> mConditionsMap.put(condition, false)));
-
-        if (callbacks == null) {
-            return;
-        }
-
-        for (Callback callback : callbacks) {
-            addCallback(callback);
-        }
     }
 
-    @Override
-    public void addCallback(@NotNull Callback callback) {
+    private void addConditionLocked(@NotNull Condition condition) {
+        mConditions.add(condition);
+
+        if (!mHaveConditionsStarted) {
+            return;
+        }
+
+        condition.addCallback(mConditionCallback);
+        updateConditionMetState();
+    }
+
+    /**
+     * Adds a condition for the monitor to listen to and consider when determining whether the
+     * overall condition state is met.
+     */
+    public void addCondition(@NotNull Condition condition) {
+        mExecutor.execute(() -> addConditionLocked(condition));
+    }
+
+    /**
+     * Removes a condition from further consideration.
+     */
+    public void removeCondition(@NotNull Condition condition) {
+        mExecutor.execute(() -> {
+            mConditions.remove(condition);
+
+            if (!mHaveConditionsStarted) {
+                return;
+            }
+
+            condition.removeCallback(mConditionCallback);
+            updateConditionMetState();
+        });
+    }
+
+    private void addCallbackLocked(@NotNull Callback callback) {
         if (shouldLog()) Log.d(mTag, "adding callback");
         mCallbacks.add(callback);
 
@@ -109,36 +154,36 @@
         if (!mHaveConditionsStarted) {
             if (shouldLog()) Log.d(mTag, "starting all conditions");
             mConditions.forEach(condition -> condition.addCallback(mConditionCallback));
+            updateConditionMetState();
             mHaveConditionsStarted = true;
         }
     }
 
     @Override
-    public void removeCallback(@NotNull Callback callback) {
-        if (shouldLog()) Log.d(mTag, "removing callback");
-        final Iterator<Callback> iterator = mCallbacks.iterator();
-        while (iterator.hasNext()) {
-            final Callback cb = iterator.next();
-            if (cb == null || cb == callback) {
-                iterator.remove();
-            }
-        }
-
-        if (mCallbacks.isEmpty() && mHaveConditionsStarted) {
-            if (shouldLog()) Log.d(mTag, "stopping all conditions");
-            mConditions.forEach(condition -> condition.removeCallback(mConditionCallback));
-
-            mAllConditionsMet = false;
-            mHaveConditionsStarted = false;
-        }
+    public void addCallback(@NotNull Callback callback) {
+        mExecutor.execute(() -> addCallbackLocked(callback));
     }
 
-    /**
-     * Force updates each condition to the value provided.
-     */
-    @VisibleForTesting
-    public void overrideAllConditionsMet(boolean value) {
-        mConditions.forEach(condition -> condition.updateCondition(value));
+    @Override
+    public void removeCallback(@NotNull Callback callback) {
+        mExecutor.execute(() -> {
+            if (shouldLog()) Log.d(mTag, "removing callback");
+            final Iterator<Callback> iterator = mCallbacks.iterator();
+            while (iterator.hasNext()) {
+                final Callback cb = iterator.next();
+                if (cb == null || cb == callback) {
+                    iterator.remove();
+                }
+            }
+
+            if (mCallbacks.isEmpty() && mHaveConditionsStarted) {
+                if (shouldLog()) Log.d(mTag, "stopping all conditions");
+                mConditions.forEach(condition -> condition.removeCallback(mConditionCallback));
+
+                mAllConditionsMet = false;
+                mHaveConditionsStarted = false;
+            }
+        });
     }
 
     private boolean shouldLog() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
index b64d7be..d8de07d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
@@ -21,6 +21,7 @@
 import com.android.internal.view.RotationPolicy
 import com.android.internal.view.RotationPolicy.RotationPolicyListener
 import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.traceSection
 import javax.inject.Inject
 
 /**
@@ -44,7 +45,9 @@
         RotationPolicyWrapper {
 
     override fun setRotationLock(enabled: Boolean) {
-        RotationPolicy.setRotationLock(context, enabled)
+        traceSection("RotationPolicyWrapperImpl#setRotationLock") {
+            RotationPolicy.setRotationLock(context, enabled)
+        }
     }
 
     override fun setRotationLockAtAngle(enabled: Boolean, rotation: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index c083c14..955d616 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -22,6 +22,7 @@
 import android.content.res.Configuration;
 import android.media.VolumePolicy;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.view.WindowManager.LayoutParams;
 
 import com.android.settingslib.applications.InterestingConfigChanges;
@@ -59,6 +60,11 @@
     public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
     public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
 
+    private static final Intent ZEN_SETTINGS =
+            new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+    private static final Intent ZEN_PRIORITY_SETTINGS =
+            new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
+
     protected final Context mContext;
     private final VolumeDialogControllerImpl mController;
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
@@ -191,12 +197,12 @@
     private final VolumeDialogImpl.Callback mVolumeDialogCallback = new VolumeDialogImpl.Callback() {
         @Override
         public void onZenSettingsClicked() {
-            startSettings(ZenModePanel.ZEN_SETTINGS);
+            startSettings(ZEN_SETTINGS);
         }
 
         @Override
         public void onZenPrioritySettingsClicked() {
-            startSettings(ZenModePanel.ZEN_PRIORITY_SETTINGS);
+            startSettings(ZEN_PRIORITY_SETTINGS);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 7bb987c..e69de29 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -1,1077 +0,0 @@
-/**
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.volume;
-
-import android.animation.LayoutTransition;
-import android.animation.LayoutTransition.TransitionListener;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.Settings;
-import android.provider.Settings.Global;
-import android.service.notification.Condition;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ZenRule;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.MathUtils;
-import android.util.Slog;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.Locale;
-import java.util.Objects;
-
-public class ZenModePanel extends FrameLayout {
-    private static final String TAG = "ZenModePanel";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    public static final int STATE_MODIFY = 0;
-    public static final int STATE_AUTO_RULE = 1;
-    public static final int STATE_OFF = 2;
-
-    private static final int SECONDS_MS = 1000;
-    private static final int MINUTES_MS = 60 * SECONDS_MS;
-
-    private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
-    private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
-    private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
-    private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
-    private static final int FOREVER_CONDITION_INDEX = 0;
-    private static final int COUNTDOWN_CONDITION_INDEX = 1;
-    private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
-    private static final int COUNTDOWN_CONDITION_COUNT = 2;
-
-    public static final Intent ZEN_SETTINGS
-            = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
-    public static final Intent ZEN_PRIORITY_SETTINGS
-            = new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
-
-    private static final long TRANSITION_DURATION = 300;
-
-    private final Context mContext;
-    protected final LayoutInflater mInflater;
-    private final H mHandler = new H();
-    private final ZenPrefs mPrefs;
-    private final TransitionHelper mTransitionHelper = new TransitionHelper();
-    private final Uri mForeverId;
-    private final ConfigurableTexts mConfigurableTexts;
-
-    private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
-
-    protected SegmentedButtons mZenButtons;
-    private View mZenIntroduction;
-    private TextView mZenIntroductionMessage;
-    private View mZenIntroductionConfirm;
-    private TextView mZenIntroductionCustomize;
-    protected LinearLayout mZenConditions;
-    private TextView mZenAlarmWarning;
-    private RadioGroup mZenRadioGroup;
-    private LinearLayout mZenRadioGroupContent;
-
-    private Callback mCallback;
-    private ZenModeController mController;
-    private Condition mExitCondition;
-    private int mBucketIndex = -1;
-    private boolean mExpanded;
-    private boolean mHidden;
-    private int mSessionZen;
-    private int mAttachedZen;
-    private boolean mAttached;
-    private Condition mSessionExitCondition;
-    private boolean mVoiceCapable;
-
-    protected int mZenModeConditionLayoutId;
-    protected int mZenModeButtonLayoutId;
-    private View mEmpty;
-    private TextView mEmptyText;
-    private ImageView mEmptyIcon;
-    private View mAutoRule;
-    private TextView mAutoTitle;
-    private int mState = STATE_MODIFY;
-    private ViewGroup mEdit;
-
-    public ZenModePanel(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
-        mPrefs = new ZenPrefs();
-        mInflater = LayoutInflater.from(mContext);
-        mForeverId = Condition.newId(mContext).appendPath("forever").build();
-        mConfigurableTexts = new ConfigurableTexts(mContext);
-        mVoiceCapable = Util.isVoiceCapable(mContext);
-        mZenModeConditionLayoutId = R.layout.zen_mode_condition;
-        mZenModeButtonLayoutId = R.layout.zen_mode_button;
-        if (DEBUG) Log.d(mTag, "new ZenModePanel");
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("ZenModePanel state:");
-        pw.print("  mAttached="); pw.println(mAttached);
-        pw.print("  mHidden="); pw.println(mHidden);
-        pw.print("  mExpanded="); pw.println(mExpanded);
-        pw.print("  mSessionZen="); pw.println(mSessionZen);
-        pw.print("  mAttachedZen="); pw.println(mAttachedZen);
-        pw.print("  mConfirmedPriorityIntroduction=");
-        pw.println(mPrefs.mConfirmedPriorityIntroduction);
-        pw.print("  mConfirmedSilenceIntroduction=");
-        pw.println(mPrefs.mConfirmedSilenceIntroduction);
-        pw.print("  mVoiceCapable="); pw.println(mVoiceCapable);
-        mTransitionHelper.dump(fd, pw, args);
-    }
-
-    protected void createZenButtons() {
-        mZenButtons = findViewById(R.id.zen_buttons);
-        mZenButtons.addButton(R.string.interruption_level_none_twoline,
-                R.string.interruption_level_none_with_warning,
-                Global.ZEN_MODE_NO_INTERRUPTIONS);
-        mZenButtons.addButton(R.string.interruption_level_alarms_twoline,
-                R.string.interruption_level_alarms,
-                Global.ZEN_MODE_ALARMS);
-        mZenButtons.addButton(R.string.interruption_level_priority_twoline,
-                R.string.interruption_level_priority,
-                Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-        mZenButtons.setCallback(mZenButtonsCallback);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        createZenButtons();
-        mZenIntroduction = findViewById(R.id.zen_introduction);
-        mZenIntroductionMessage = findViewById(R.id.zen_introduction_message);
-        mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm);
-        mZenIntroductionConfirm.setOnClickListener(v -> confirmZenIntroduction());
-        mZenIntroductionCustomize = findViewById(R.id.zen_introduction_customize);
-        mZenIntroductionCustomize.setOnClickListener(v -> {
-            confirmZenIntroduction();
-            if (mCallback != null) {
-                mCallback.onPrioritySettings();
-            }
-        });
-        mConfigurableTexts.add(mZenIntroductionCustomize, R.string.zen_priority_customize_button);
-
-        mZenConditions = findViewById(R.id.zen_conditions);
-        mZenAlarmWarning = findViewById(R.id.zen_alarm_warning);
-        mZenRadioGroup = findViewById(R.id.zen_radio_buttons);
-        mZenRadioGroupContent = findViewById(R.id.zen_radio_buttons_content);
-
-        mEdit = findViewById(R.id.edit_container);
-
-        mEmpty = findViewById(android.R.id.empty);
-        mEmpty.setVisibility(INVISIBLE);
-        mEmptyText = mEmpty.findViewById(android.R.id.title);
-        mEmptyIcon = mEmpty.findViewById(android.R.id.icon);
-
-        mAutoRule = findViewById(R.id.auto_rule);
-        mAutoTitle = mAutoRule.findViewById(android.R.id.title);
-        mAutoRule.setVisibility(INVISIBLE);
-    }
-
-    public void setEmptyState(int icon, int text) {
-        mEmptyIcon.post(() -> {
-            mEmptyIcon.setImageResource(icon);
-            mEmptyText.setText(text);
-        });
-    }
-
-    public void setAutoText(CharSequence text) {
-        mAutoTitle.post(() -> mAutoTitle.setText(text));
-    }
-
-    public void setState(int state) {
-        if (mState == state) return;
-        transitionFrom(getView(mState), getView(state));
-        mState = state;
-    }
-
-    private void transitionFrom(View from, View to) {
-        from.post(() -> {
-            // TODO: Better transitions
-            to.setAlpha(0);
-            to.setVisibility(VISIBLE);
-            to.bringToFront();
-            to.animate().cancel();
-            to.animate().alpha(1)
-                    .setDuration(TRANSITION_DURATION)
-                    .withEndAction(() -> from.setVisibility(INVISIBLE))
-                    .start();
-        });
-    }
-
-    private View getView(int state) {
-        switch (state) {
-            case STATE_AUTO_RULE:
-                return mAutoRule;
-            case STATE_OFF:
-                return mEmpty;
-            default:
-                return mEdit;
-        }
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        mConfigurableTexts.update();
-        if (mZenButtons != null) {
-            mZenButtons.update();
-        }
-    }
-
-    private void confirmZenIntroduction() {
-        final String prefKey = prefKeyForConfirmation(getSelectedZen(Global.ZEN_MODE_OFF));
-        if (prefKey == null) return;
-        if (DEBUG) Log.d(TAG, "confirmZenIntroduction " + prefKey);
-        Prefs.putBoolean(mContext, prefKey, true);
-        mHandler.sendEmptyMessage(H.UPDATE_WIDGETS);
-    }
-
-    private static String prefKeyForConfirmation(int zen) {
-        switch (zen) {
-            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
-                return Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION;
-            case Global.ZEN_MODE_NO_INTERRUPTIONS:
-                return Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION;
-            case Global.ZEN_MODE_ALARMS:
-                return Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION;
-            default:
-                return null;
-        }
-    }
-
-    private void onAttach() {
-        setExpanded(true);
-        mAttachedZen = mController.getZen();
-        ZenRule manualRule = mController.getManualRule();
-        mExitCondition = manualRule != null ? manualRule.condition : null;
-        if (DEBUG) Log.d(mTag, "onAttach " + mAttachedZen + " " + manualRule);
-        handleUpdateManualRule(manualRule);
-        mZenButtons.setSelectedValue(mAttachedZen, false);
-        mSessionZen = mAttachedZen;
-        mTransitionHelper.clear();
-        mController.addCallback(mZenCallback);
-        setSessionExitCondition(copy(mExitCondition));
-        updateWidgets();
-        setAttached(true);
-    }
-
-    private void onDetach() {
-        if (DEBUG) Log.d(mTag, "onDetach");
-        setExpanded(false);
-        checkForAttachedZenChange();
-        setAttached(false);
-        mAttachedZen = -1;
-        mSessionZen = -1;
-        mController.removeCallback(mZenCallback);
-        setSessionExitCondition(null);
-        mTransitionHelper.clear();
-    }
-
-    @VisibleForTesting
-    void setAttached(boolean attached) {
-        mAttached = attached;
-    }
-
-    @Override
-    public void onVisibilityAggregated(boolean isVisible) {
-        super.onVisibilityAggregated(isVisible);
-        if (isVisible == mAttached) return;
-        if (isVisible) {
-            onAttach();
-        } else {
-            onDetach();
-        }
-    }
-
-    private void setSessionExitCondition(Condition condition) {
-        if (Objects.equals(condition, mSessionExitCondition)) return;
-        if (DEBUG) Log.d(mTag, "mSessionExitCondition=" + getConditionId(condition));
-        mSessionExitCondition = condition;
-    }
-
-    public void setHidden(boolean hidden) {
-        if (mHidden == hidden) return;
-        if (DEBUG) Log.d(mTag, "hidden=" + hidden);
-        mHidden = hidden;
-        updateWidgets();
-    }
-
-    private void checkForAttachedZenChange() {
-        final int selectedZen = getSelectedZen(-1);
-        if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen);
-        if (selectedZen != mAttachedZen) {
-            if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen);
-            if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
-                mPrefs.trackNoneSelected();
-            }
-        }
-    }
-
-    private void setExpanded(boolean expanded) {
-        if (expanded == mExpanded) return;
-        if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
-        mExpanded = expanded;
-        updateWidgets();
-        fireExpanded();
-    }
-
-    protected void addZenConditions(int count) {
-        for (int i = 0; i < count; i++) {
-            final View rb = mInflater.inflate(mZenModeButtonLayoutId, mEdit, false);
-            rb.setId(i);
-            mZenRadioGroup.addView(rb);
-            final View rbc = mInflater.inflate(mZenModeConditionLayoutId, mEdit, false);
-            rbc.setId(i + count);
-            mZenRadioGroupContent.addView(rbc);
-        }
-    }
-
-    public void init(ZenModeController controller) {
-        mController = controller;
-        final int minConditions = 1 /*forever*/ + COUNTDOWN_CONDITION_COUNT;
-        addZenConditions(minConditions);
-        mSessionZen = getSelectedZen(-1);
-        handleUpdateManualRule(mController.getManualRule());
-        if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
-        hideAllConditions();
-    }
-
-    private void setExitCondition(Condition exitCondition) {
-        if (Objects.equals(mExitCondition, exitCondition)) return;
-        mExitCondition = exitCondition;
-        if (DEBUG) Log.d(mTag, "mExitCondition=" + getConditionId(mExitCondition));
-        updateWidgets();
-    }
-
-    private static Uri getConditionId(Condition condition) {
-        return condition != null ? condition.id : null;
-    }
-
-    private Uri getRealConditionId(Condition condition) {
-        return isForever(condition) ? null : getConditionId(condition);
-    }
-
-    private static Condition copy(Condition condition) {
-        return condition == null ? null : condition.copy();
-    }
-
-    public void setCallback(Callback callback) {
-        mCallback = callback;
-    }
-
-    @VisibleForTesting
-    void handleUpdateManualRule(ZenRule rule) {
-        final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
-        handleUpdateZen(zen);
-        final Condition c = rule == null ? null
-                : rule.condition != null ? rule.condition
-                : createCondition(rule.conditionId);
-        handleUpdateConditions(c);
-        setExitCondition(c);
-    }
-
-    private Condition createCondition(Uri conditionId) {
-        if (ZenModeConfig.isValidCountdownToAlarmConditionId(conditionId)) {
-            long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
-            Condition c = ZenModeConfig.toNextAlarmCondition(
-                    mContext, time, ActivityManager.getCurrentUser());
-            return c;
-        } else if (ZenModeConfig.isValidCountdownConditionId(conditionId)) {
-            long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
-            int mins = (int) ((time - System.currentTimeMillis() + DateUtils.MINUTE_IN_MILLIS / 2)
-                    / DateUtils.MINUTE_IN_MILLIS);
-            Condition c = ZenModeConfig.toTimeCondition(mContext, time, mins,
-                    ActivityManager.getCurrentUser(), false);
-            return c;
-        }
-        // If there is a manual rule, but it has no condition listed then it is forever.
-        return forever();
-    }
-
-    private void handleUpdateZen(int zen) {
-        if (mSessionZen != -1 && mSessionZen != zen) {
-            mSessionZen = zen;
-        }
-        mZenButtons.setSelectedValue(zen, false /* fromClick */);
-        updateWidgets();
-    }
-
-    @VisibleForTesting
-    int getSelectedZen(int defValue) {
-        final Object zen = mZenButtons.getSelectedValue();
-        return zen != null ? (Integer) zen : defValue;
-    }
-
-    private void updateWidgets() {
-        if (mTransitionHelper.isTransitioning()) {
-            mTransitionHelper.pendingUpdateWidgets();
-            return;
-        }
-        final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
-        final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
-        final boolean zenAlarm = zen == Global.ZEN_MODE_ALARMS;
-        final boolean introduction = (zenImportant && !mPrefs.mConfirmedPriorityIntroduction
-                || zenNone && !mPrefs.mConfirmedSilenceIntroduction
-                || zenAlarm && !mPrefs.mConfirmedAlarmIntroduction);
-
-        mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
-        mZenIntroduction.setVisibility(introduction ? VISIBLE : GONE);
-        if (introduction) {
-            int message = zenImportant
-                    ? R.string.zen_priority_introduction
-                    : zenAlarm
-                            ? R.string.zen_alarms_introduction
-                            : mVoiceCapable
-                                    ? R.string.zen_silence_introduction_voice
-                                    : R.string.zen_silence_introduction;
-            mConfigurableTexts.add(mZenIntroductionMessage, message);
-            mConfigurableTexts.update();
-            mZenIntroductionCustomize.setVisibility(zenImportant ? VISIBLE : GONE);
-        }
-        final String warning = computeAlarmWarningText(zenNone);
-        mZenAlarmWarning.setVisibility(warning != null ? VISIBLE : GONE);
-        mZenAlarmWarning.setText(warning);
-    }
-
-    private String computeAlarmWarningText(boolean zenNone) {
-        if (!zenNone) {
-            return null;
-        }
-        final long now = System.currentTimeMillis();
-        final long nextAlarm = mController.getNextAlarm();
-        if (nextAlarm < now) {
-            return null;
-        }
-        int warningRes = 0;
-        if (mSessionExitCondition == null || isForever(mSessionExitCondition)) {
-            warningRes = R.string.zen_alarm_warning_indef;
-        } else {
-            final long time = ZenModeConfig.tryParseCountdownConditionId(mSessionExitCondition.id);
-            if (time > now && nextAlarm < time) {
-                warningRes = R.string.zen_alarm_warning;
-            }
-        }
-        if (warningRes == 0) {
-            return null;
-        }
-        final boolean soon = (nextAlarm - now) < 24 * 60 * 60 * 1000;
-        final boolean is24 = DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
-        final String skeleton = soon ? (is24 ? "Hm" : "hma") : (is24 ? "EEEHm" : "EEEhma");
-        final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
-        final CharSequence formattedTime = DateFormat.format(pattern, nextAlarm);
-        final int templateRes = soon ? R.string.alarm_template : R.string.alarm_template_far;
-        final String template = getResources().getString(templateRes, formattedTime);
-        return getResources().getString(warningRes, template);
-    }
-
-    @VisibleForTesting
-    void handleUpdateConditions(Condition c) {
-        if (mTransitionHelper.isTransitioning()) {
-            return;
-        }
-        // forever
-        bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX),
-                FOREVER_CONDITION_INDEX);
-        if (c == null) {
-            bindGenericCountdown();
-            bindNextAlarm(getTimeUntilNextAlarmCondition());
-        } else if (isForever(c)) {
-
-            getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
-            bindGenericCountdown();
-            bindNextAlarm(getTimeUntilNextAlarmCondition());
-        } else {
-            if (isAlarm(c)) {
-                bindGenericCountdown();
-                bindNextAlarm(c);
-                getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true);
-            } else if (isCountdown(c)) {
-                bindNextAlarm(getTimeUntilNextAlarmCondition());
-                bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
-                        COUNTDOWN_CONDITION_INDEX);
-                getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
-            } else {
-                Slog.wtf(TAG, "Invalid manual condition: " + c);
-            }
-        }
-        mZenConditions.setVisibility(mSessionZen != Global.ZEN_MODE_OFF ? View.VISIBLE : View.GONE);
-    }
-
-    private void bindGenericCountdown() {
-        mBucketIndex = DEFAULT_BUCKET_INDEX;
-        Condition countdown = ZenModeConfig.toTimeCondition(mContext,
-                MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
-        // don't change the hour condition while the user is viewing the panel
-        if (!mAttached || getConditionTagAt(COUNTDOWN_CONDITION_INDEX).condition == null) {
-            bind(countdown, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
-                    COUNTDOWN_CONDITION_INDEX);
-        }
-    }
-
-    private void bindNextAlarm(Condition c) {
-        View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX);
-        ConditionTag tag = (ConditionTag) alarmContent.getTag();
-        // Don't change the alarm condition while the user is viewing the panel
-        if (c != null && (!mAttached || tag == null || tag.condition == null)) {
-            bind(c, alarmContent, COUNTDOWN_ALARM_CONDITION_INDEX);
-        }
-
-        tag = (ConditionTag) alarmContent.getTag();
-        boolean showAlarm = tag != null && tag.condition != null;
-        mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(
-                showAlarm ? View.VISIBLE : View.INVISIBLE);
-        alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    private Condition forever() {
-        return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/,
-                Condition.STATE_TRUE, 0 /*flags*/);
-    }
-
-    private static String foreverSummary(Context context) {
-        return context.getString(com.android.internal.R.string.zen_mode_forever);
-    }
-
-    // Returns a time condition if the next alarm is within the next week.
-    private Condition getTimeUntilNextAlarmCondition() {
-        GregorianCalendar weekRange = new GregorianCalendar();
-        setToMidnight(weekRange);
-        weekRange.add(Calendar.DATE, 6);
-        final long nextAlarmMs = mController.getNextAlarm();
-        if (nextAlarmMs > 0) {
-            GregorianCalendar nextAlarm = new GregorianCalendar();
-            nextAlarm.setTimeInMillis(nextAlarmMs);
-            setToMidnight(nextAlarm);
-
-            if (weekRange.compareTo(nextAlarm) >= 0) {
-                return ZenModeConfig.toNextAlarmCondition(mContext, nextAlarmMs,
-                        ActivityManager.getCurrentUser());
-            }
-        }
-        return null;
-    }
-
-    private void setToMidnight(Calendar calendar) {
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.set(Calendar.MILLISECOND, 0);
-    }
-
-    @VisibleForTesting
-    ConditionTag getConditionTagAt(int index) {
-        return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
-    }
-
-    @VisibleForTesting
-    int getVisibleConditions() {
-        int rt = 0;
-        final int N = mZenRadioGroupContent.getChildCount();
-        for (int i = 0; i < N; i++) {
-            rt += mZenRadioGroupContent.getChildAt(i).getVisibility() == VISIBLE ? 1 : 0;
-        }
-        return rt;
-    }
-
-    private void hideAllConditions() {
-        final int N = mZenRadioGroupContent.getChildCount();
-        for (int i = 0; i < N; i++) {
-            mZenRadioGroupContent.getChildAt(i).setVisibility(GONE);
-        }
-    }
-
-    private static boolean isAlarm(Condition c) {
-        return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id);
-    }
-
-    private static boolean isCountdown(Condition c) {
-        return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
-    }
-
-    private boolean isForever(Condition c) {
-        return c != null && mForeverId.equals(c.id);
-    }
-
-    private void bind(final Condition condition, final View row, final int rowId) {
-        if (condition == null) throw new IllegalArgumentException("condition must not be null");
-        final boolean enabled = condition.state == Condition.STATE_TRUE;
-        final ConditionTag tag =
-                row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
-        row.setTag(tag);
-        final boolean first = tag.rb == null;
-        if (tag.rb == null) {
-            tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowId);
-        }
-        tag.condition = condition;
-        final Uri conditionId = getConditionId(tag.condition);
-        if (DEBUG) Log.d(mTag, "bind i=" + mZenRadioGroupContent.indexOfChild(row) + " first="
-                + first + " condition=" + conditionId);
-        tag.rb.setEnabled(enabled);
-        tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
-            @Override
-            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-                if (mExpanded && isChecked) {
-                    tag.rb.setChecked(true);
-                    if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId);
-                    MetricsLogger.action(mContext, MetricsEvent.QS_DND_CONDITION_SELECT);
-                    select(tag.condition);
-                    announceConditionSelection(tag);
-                }
-            }
-        });
-
-        if (tag.lines == null) {
-            tag.lines = row.findViewById(android.R.id.content);
-        }
-        if (tag.line1 == null) {
-            tag.line1 = (TextView) row.findViewById(android.R.id.text1);
-            mConfigurableTexts.add(tag.line1);
-        }
-        if (tag.line2 == null) {
-            tag.line2 = (TextView) row.findViewById(android.R.id.text2);
-            mConfigurableTexts.add(tag.line2);
-        }
-        final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1
-                : condition.summary;
-        final String line2 = condition.line2;
-        tag.line1.setText(line1);
-        if (TextUtils.isEmpty(line2)) {
-            tag.line2.setVisibility(GONE);
-        } else {
-            tag.line2.setVisibility(VISIBLE);
-            tag.line2.setText(line2);
-        }
-        tag.lines.setEnabled(enabled);
-        tag.lines.setAlpha(enabled ? 1 : .4f);
-
-        final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
-        button1.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                onClickTimeButton(row, tag, false /*down*/, rowId);
-            }
-        });
-
-        final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
-        button2.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                onClickTimeButton(row, tag, true /*up*/, rowId);
-            }
-        });
-        tag.lines.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                tag.rb.setChecked(true);
-            }
-        });
-
-        final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
-        if (rowId != COUNTDOWN_ALARM_CONDITION_INDEX && time > 0) {
-            button1.setVisibility(VISIBLE);
-            button2.setVisibility(VISIBLE);
-            if (mBucketIndex > -1) {
-                button1.setEnabled(mBucketIndex > 0);
-                button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
-            } else {
-                final long span = time - System.currentTimeMillis();
-                button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
-                final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext,
-                        MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser());
-                button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
-            }
-
-            button1.setAlpha(button1.isEnabled() ? 1f : .5f);
-            button2.setAlpha(button2.isEnabled() ? 1f : .5f);
-        } else {
-            button1.setVisibility(GONE);
-            button2.setVisibility(GONE);
-        }
-        // wire up interaction callbacks for newly-added condition rows
-        if (first) {
-            Interaction.register(tag.rb, mInteractionCallback);
-            Interaction.register(tag.lines, mInteractionCallback);
-            Interaction.register(button1, mInteractionCallback);
-            Interaction.register(button2, mInteractionCallback);
-        }
-        row.setVisibility(VISIBLE);
-    }
-
-    private void announceConditionSelection(ConditionTag tag) {
-        final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
-        String modeText;
-        switch(zen) {
-            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
-                modeText = mContext.getString(R.string.interruption_level_priority);
-                break;
-            case Global.ZEN_MODE_NO_INTERRUPTIONS:
-                modeText = mContext.getString(R.string.interruption_level_none);
-                break;
-            case Global.ZEN_MODE_ALARMS:
-                modeText = mContext.getString(R.string.interruption_level_alarms);
-                break;
-            default:
-                return;
-        }
-        announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText,
-                tag.line1.getText()));
-    }
-
-    private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
-        MetricsLogger.action(mContext, MetricsEvent.QS_DND_TIME, up);
-        Condition newCondition = null;
-        final int N = MINUTE_BUCKETS.length;
-        if (mBucketIndex == -1) {
-            // not on a known index, search for the next or prev bucket by time
-            final Uri conditionId = getConditionId(tag.condition);
-            final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
-            final long now = System.currentTimeMillis();
-            for (int i = 0; i < N; i++) {
-                int j = up ? i : N - 1 - i;
-                final int bucketMinutes = MINUTE_BUCKETS[j];
-                final long bucketTime = now + bucketMinutes * MINUTES_MS;
-                if (up && bucketTime > time || !up && bucketTime < time) {
-                    mBucketIndex = j;
-                    newCondition = ZenModeConfig.toTimeCondition(mContext,
-                            bucketTime, bucketMinutes, ActivityManager.getCurrentUser(),
-                            false /*shortVersion*/);
-                    break;
-                }
-            }
-            if (newCondition == null) {
-                mBucketIndex = DEFAULT_BUCKET_INDEX;
-                newCondition = ZenModeConfig.toTimeCondition(mContext,
-                        MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
-            }
-        } else {
-            // on a known index, simply increment or decrement
-            mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
-            newCondition = ZenModeConfig.toTimeCondition(mContext,
-                    MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
-        }
-        bind(newCondition, row, rowId);
-        tag.rb.setChecked(true);
-        select(newCondition);
-        announceConditionSelection(tag);
-    }
-
-    private void select(final Condition condition) {
-        if (DEBUG) Log.d(mTag, "select " + condition);
-        if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) {
-            if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen");
-            return;
-        }
-        final Uri realConditionId = getRealConditionId(condition);
-        if (mController != null) {
-            AsyncTask.execute(new Runnable() {
-                @Override
-                public void run() {
-                    mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition");
-                }
-            });
-        }
-        setExitCondition(condition);
-        if (realConditionId == null) {
-            mPrefs.setMinuteIndex(-1);
-        } else if ((isAlarm(condition) || isCountdown(condition)) && mBucketIndex != -1) {
-            mPrefs.setMinuteIndex(mBucketIndex);
-        }
-        setSessionExitCondition(copy(condition));
-    }
-
-    private void fireInteraction() {
-        if (mCallback != null) {
-            mCallback.onInteraction();
-        }
-    }
-
-    private void fireExpanded() {
-        if (mCallback != null) {
-            mCallback.onExpanded(mExpanded);
-        }
-    }
-
-    private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
-        @Override
-        public void onManualRuleChanged(ZenRule rule) {
-            mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
-        }
-    };
-
-    private final class H extends Handler {
-        private static final int MANUAL_RULE_CHANGED = 2;
-        private static final int UPDATE_WIDGETS = 3;
-
-        private H() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break;
-                case UPDATE_WIDGETS: updateWidgets(); break;
-            }
-        }
-    }
-
-    public interface Callback {
-        void onPrioritySettings();
-        void onInteraction();
-        void onExpanded(boolean expanded);
-    }
-
-    // used as the view tag on condition rows
-    @VisibleForTesting
-    static class ConditionTag {
-        RadioButton rb;
-        View lines;
-        TextView line1;
-        TextView line2;
-        Condition condition;
-    }
-
-    private final class ZenPrefs implements OnSharedPreferenceChangeListener {
-        private final int mNoneDangerousThreshold;
-
-        private int mMinuteIndex;
-        private int mNoneSelected;
-        private boolean mConfirmedPriorityIntroduction;
-        private boolean mConfirmedSilenceIntroduction;
-        private boolean mConfirmedAlarmIntroduction;
-
-        private ZenPrefs() {
-            mNoneDangerousThreshold = mContext.getResources()
-                    .getInteger(R.integer.zen_mode_alarm_warning_threshold);
-            Prefs.registerListener(mContext, this);
-            updateMinuteIndex();
-            updateNoneSelected();
-            updateConfirmedPriorityIntroduction();
-            updateConfirmedSilenceIntroduction();
-            updateConfirmedAlarmIntroduction();
-        }
-
-        public void trackNoneSelected() {
-            mNoneSelected = clampNoneSelected(mNoneSelected + 1);
-            if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
-                    + mNoneDangerousThreshold);
-            Prefs.putInt(mContext, Prefs.Key.DND_NONE_SELECTED, mNoneSelected);
-        }
-
-        public int getMinuteIndex() {
-            return mMinuteIndex;
-        }
-
-        public void setMinuteIndex(int minuteIndex) {
-            minuteIndex = clampIndex(minuteIndex);
-            if (minuteIndex == mMinuteIndex) return;
-            mMinuteIndex = clampIndex(minuteIndex);
-            if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
-            Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_BUCKET_INDEX, mMinuteIndex);
-        }
-
-        @Override
-        public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
-            updateMinuteIndex();
-            updateNoneSelected();
-            updateConfirmedPriorityIntroduction();
-            updateConfirmedSilenceIntroduction();
-            updateConfirmedAlarmIntroduction();
-        }
-
-        private void updateMinuteIndex() {
-            mMinuteIndex = clampIndex(Prefs.getInt(mContext,
-                    Prefs.Key.DND_FAVORITE_BUCKET_INDEX, DEFAULT_BUCKET_INDEX));
-            if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
-        }
-
-        private int clampIndex(int index) {
-            return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
-        }
-
-        private void updateNoneSelected() {
-            mNoneSelected = clampNoneSelected(Prefs.getInt(mContext,
-                    Prefs.Key.DND_NONE_SELECTED, 0));
-            if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
-        }
-
-        private int clampNoneSelected(int noneSelected) {
-            return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
-        }
-
-        private void updateConfirmedPriorityIntroduction() {
-            final boolean confirmed =  Prefs.getBoolean(mContext,
-                    Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION, false);
-            if (confirmed == mConfirmedPriorityIntroduction) return;
-            mConfirmedPriorityIntroduction = confirmed;
-            if (DEBUG) Log.d(mTag, "Confirmed priority introduction: "
-                    + mConfirmedPriorityIntroduction);
-        }
-
-        private void updateConfirmedSilenceIntroduction() {
-            final boolean confirmed =  Prefs.getBoolean(mContext,
-                    Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION, false);
-            if (confirmed == mConfirmedSilenceIntroduction) return;
-            mConfirmedSilenceIntroduction = confirmed;
-            if (DEBUG) Log.d(mTag, "Confirmed silence introduction: "
-                    + mConfirmedSilenceIntroduction);
-        }
-
-        private void updateConfirmedAlarmIntroduction() {
-            final boolean confirmed =  Prefs.getBoolean(mContext,
-                    Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, false);
-            if (confirmed == mConfirmedAlarmIntroduction) return;
-            mConfirmedAlarmIntroduction = confirmed;
-            if (DEBUG) Log.d(mTag, "Confirmed alarm introduction: "
-                    + mConfirmedAlarmIntroduction);
-        }
-    }
-
-    protected final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
-        @Override
-        public void onSelected(final Object value, boolean fromClick) {
-            if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
-                final int zen = (Integer) value;
-                if (fromClick) {
-                    MetricsLogger.action(mContext, MetricsEvent.QS_DND_ZEN_SELECT, zen);
-                }
-                if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + zen);
-                final Uri realConditionId = getRealConditionId(mSessionExitCondition);
-                AsyncTask.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        mController.setZen(zen, realConditionId, TAG + ".selectZen");
-                        if (zen != Global.ZEN_MODE_OFF) {
-                            Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, zen);
-                        }
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onInteraction() {
-            fireInteraction();
-        }
-    };
-
-    private final Interaction.Callback mInteractionCallback = new Interaction.Callback() {
-        @Override
-        public void onInteraction() {
-            fireInteraction();
-        }
-    };
-
-    private final class TransitionHelper implements TransitionListener, Runnable {
-        private final ArraySet<View> mTransitioningViews = new ArraySet<View>();
-
-        private boolean mTransitioning;
-        private boolean mPendingUpdateWidgets;
-
-        public void clear() {
-            mTransitioningViews.clear();
-            mPendingUpdateWidgets = false;
-        }
-
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            pw.println("  TransitionHelper state:");
-            pw.print("    mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets);
-            pw.print("    mTransitioning="); pw.println(mTransitioning);
-            pw.print("    mTransitioningViews="); pw.println(mTransitioningViews);
-        }
-
-        public void pendingUpdateWidgets() {
-            mPendingUpdateWidgets = true;
-        }
-
-        public boolean isTransitioning() {
-            return !mTransitioningViews.isEmpty();
-        }
-
-        @Override
-        public void startTransition(LayoutTransition transition,
-                ViewGroup container, View view, int transitionType) {
-            mTransitioningViews.add(view);
-            updateTransitioning();
-        }
-
-        @Override
-        public void endTransition(LayoutTransition transition,
-                ViewGroup container, View view, int transitionType) {
-            mTransitioningViews.remove(view);
-            updateTransitioning();
-        }
-
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(mTag, "TransitionHelper run"
-                    + " mPendingUpdateWidgets=" + mPendingUpdateWidgets);
-            if (mPendingUpdateWidgets) {
-                updateWidgets();
-            }
-            mPendingUpdateWidgets = false;
-        }
-
-        private void updateTransitioning() {
-            final boolean transitioning = isTransitioning();
-            if (mTransitioning == transitioning) return;
-            mTransitioning = transitioning;
-            if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning);
-            if (!mTransitioning) {
-                if (mPendingUpdateWidgets) {
-                    mHandler.post(this);
-                } else {
-                    mPendingUpdateWidgets = false;
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 24b01e0..6736bfd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -307,7 +307,8 @@
             UserInfo info = new UserInfo(i /* id */, "Name: " + i, null /* iconPath */,
                     0 /* flags */);
             users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
-                    false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */));
+                    false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
+                    false /* isAddSupervisedUser */));
         }
         return users;
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index 9f8f6c1..06082b6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,6 +67,11 @@
         mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
     }
 
+    @After
+    public void tearDown() {
+        mController.onViewDetached();
+    }
+
     @Test
     public void refresh_replacesSliceContentAndNotifiesListener() {
         mController.refresh();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 08d881f..f71dd24 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -32,7 +32,6 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -58,7 +57,6 @@
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
-import android.media.AudioManager;
 import android.nfc.NfcAdapter;
 import android.os.Bundle;
 import android.os.Handler;
@@ -74,9 +72,6 @@
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
 
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
-
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.telephony.TelephonyIntents;
@@ -92,7 +87,6 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.util.RingerModeTracker;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -101,7 +95,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
@@ -161,10 +154,6 @@
     @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
-    private RingerModeTracker mRingerModeTracker;
-    @Mock
-    private LiveData<Integer> mRingerModeLiveData;
-    @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
     private AuthController mAuthController;
@@ -242,8 +231,6 @@
         mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
         mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
 
-        when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
-
         mMockitoSession = ExtendedMockito.mockitoSession()
                 .spyStatic(SubscriptionManager.class).startMocking();
         ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
@@ -866,29 +853,6 @@
     }
 
     @Test
-    public void testRingerModeChange() {
-        ArgumentCaptor<Observer<Integer>> captor = ArgumentCaptor.forClass(Observer.class);
-        verify(mRingerModeLiveData).observeForever(captor.capture());
-        Observer<Integer> observer = captor.getValue();
-
-        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
-
-        mKeyguardUpdateMonitor.registerCallback(callback);
-
-        observer.onChanged(AudioManager.RINGER_MODE_NORMAL);
-        observer.onChanged(AudioManager.RINGER_MODE_SILENT);
-        observer.onChanged(AudioManager.RINGER_MODE_VIBRATE);
-
-        mTestableLooper.processAllMessages();
-
-        InOrder orderVerify = inOrder(callback);
-        orderVerify.verify(callback).onRingerModeChanged(anyInt()); // Initial update on register
-        orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_NORMAL);
-        orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_SILENT);
-        orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_VIBRATE);
-    }
-
-    @Test
     public void testRegisterAuthControllerCallback() {
         assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isFalse();
 
@@ -1120,7 +1084,7 @@
             super(context,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
                     mBroadcastDispatcher, mDumpManager,
-                    mRingerModeTracker, mBackgroundExecutor, mMainExecutor,
+                    mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
                     mInteractionJankMonitor, mLatencyTracker);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
index 2d52c42..c5b1a1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.communal;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -59,7 +60,8 @@
 
         final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
-        verify(callback).onConditionChanged(mCondition, true);
+        verify(callback).onConditionChanged(mCondition);
+        assertThat(mCondition.isConditionMet()).isTrue();
     }
 
     @Test
@@ -68,7 +70,7 @@
 
         final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
-        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+        verify(callback, never()).onConditionChanged(eq(mCondition));
     }
 
     @Test
@@ -80,7 +82,8 @@
         clearInvocations(callback);
 
         updateCommunalSetting(true);
-        verify(callback).onConditionChanged(mCondition, true);
+        verify(callback).onConditionChanged(mCondition);
+        assertThat(mCondition.isConditionMet()).isTrue();
     }
 
     @Test
@@ -92,7 +95,8 @@
         clearInvocations(callback);
 
         updateCommunalSetting(false);
-        verify(callback).onConditionChanged(mCondition, false);
+        verify(callback).onConditionChanged(mCondition);
+        assertThat(mCondition.isConditionMet()).isFalse();
     }
 
     @Test
@@ -104,7 +108,8 @@
         clearInvocations(callback);
 
         updateCommunalSetting(true);
-        verify(callback, never()).onConditionChanged(mCondition, true);
+        verify(callback, never()).onConditionChanged(mCondition);
+        assertThat(mCondition.isConditionMet()).isTrue();
     }
 
     private void updateCommunalSetting(boolean value) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
index 61a5126..500205c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.communal;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.clearInvocations;
@@ -49,6 +50,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
@@ -89,7 +91,8 @@
         networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
 
         // Verifies that the callback is triggered.
-        verify(callback).onConditionChanged(mCondition, true);
+        verify(callback).onConditionChanged(mCondition);
+        assertThat(mCondition.isConditionMet()).isTrue();
     }
 
     @Test
@@ -110,7 +113,7 @@
         networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi2));
 
         // Verifies that the callback is not triggered.
-        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+        verify(callback, never()).onConditionChanged(eq(mCondition));
     }
 
     @Test
@@ -126,11 +129,13 @@
         networkCallback.onAvailable(network);
         networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
 
+        Mockito.clearInvocations(callback);
         // Connected to non-trusted Wi-Fi network.
         networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities("random-wifi"));
 
         // Verifies that the callback is triggered.
-        verify(callback).onConditionChanged(mCondition, false);
+        verify(callback).onConditionChanged(mCondition);
+        assertThat(mCondition.isConditionMet()).isFalse();
     }
 
     @Test
@@ -151,7 +156,8 @@
         networkCallback.onLost(network);
 
         // Verifies that the callback is triggered.
-        verify(callback).onConditionChanged(mCondition, false);
+        verify(callback).onConditionChanged(mCondition);
+        assertThat(mCondition.isConditionMet()).isFalse();
     }
 
     // Captures and returns the network callback, assuming it is registered with the connectivity
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index cdffaec..7e1edd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -35,6 +35,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.anyObject;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
@@ -306,4 +307,11 @@
         // THEN the display screen state will change
         assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
     }
+
+    @Test
+    public void authCallbackRemovedOnDestroy() {
+        mScreen.destroy();
+
+        verify(mAuthController).removeCallback(anyObject());
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 8adb55b..529a163 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -31,6 +31,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
 
+import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 import androidx.test.filters.SmallTest;
@@ -191,4 +192,14 @@
                         | Complication.COMPLICATION_TYPE_WEATHER;
         verify(mStateController).setAvailableComplicationTypes(expectedTypes);
     }
+
+    @Test
+    public void testDestroy() {
+        mService.onDestroy();
+        mMainExecutor.runAllReady();
+
+        verify(mKeyguardUpdateMonitor).removeCallback(any());
+        verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
+        verify(mStateController).setOverlayActive(false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 9e67eda..57fbbc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -62,7 +62,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer)
+        dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer, mutableMapOf())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 210cb82..a80aed7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -18,6 +18,9 @@
 
 import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
 
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -43,6 +46,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
+import com.android.keyguard.KeyguardSecurityView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SysuiTestCase;
@@ -181,6 +185,24 @@
         verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
     }
 
+    @Test
+    public void testBouncerPrompt_deviceLockedByAdmin() {
+        // GIVEN no trust agents enabled and biometrics aren't enrolled
+        when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(false);
+        when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(false);
+
+        // WHEN the strong auth reason is AFTER_DPM_LOCK_NOW
+        KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
+                mock(KeyguardUpdateMonitor.StrongAuthTracker.class);
+        when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker);
+        when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
+
+        // THEN the bouncer prompt reason should return PROMPT_REASON_DEVICE_ADMIN
+        assertEquals(KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN,
+                mViewMediator.mViewMediatorCallback.getBouncerPromptReason());
+    }
+
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 609291a..708fc91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -74,6 +74,7 @@
 private const val SESSION_ARTIST = "SESSION_ARTIST"
 private const val SESSION_TITLE = "SESSION_TITLE"
 private const val USER_ID = 0
+private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME"
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -131,7 +132,7 @@
 
     private lateinit var session: MediaSession
     private val device = MediaDeviceData(true, null, DEVICE_NAME)
-    private val disabledDevice = MediaDeviceData(false, null, "Disabled Device")
+    private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME)
     private lateinit var mediaData: MediaData
     private val clock = FakeSystemClock()
 
@@ -396,13 +397,12 @@
     @Test
     fun bindDisabledDevice() {
         seamless.id = 1
-        val fallbackString = context.getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
         val state = mediaData.copy(device = disabledDevice)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isFalse()
-        assertThat(seamlessText.getText()).isEqualTo(fallbackString)
-        assertThat(seamless.contentDescription).isEqualTo(fallbackString)
+        assertThat(seamlessText.getText()).isEqualTo(DISABLED_DEVICE_NAME)
+        assertThat(seamless.contentDescription).isEqualTo(DISABLED_DEVICE_NAME)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 649ee87..f4fa921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -1,6 +1,5 @@
 package com.android.systemui.media
 
-import android.app.Notification
 import android.app.Notification.MediaStyle
 import android.app.PendingIntent
 import android.app.smartspace.SmartspaceAction
@@ -240,15 +239,14 @@
 
     @Test
     fun testOnNotificationAdded_isRcn_markedRemote() {
-        val bundle = Bundle().apply {
-            putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Remote Cast Notification")
-        }
         val rcn = SbnBuilder().run {
             setPkg("com.android.systemui") // System package
             modifyNotification(context).also {
                 it.setSmallIcon(android.R.drawable.ic_media_pause)
-                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
-                it.addExtras(bundle)
+                it.setStyle(MediaStyle().apply {
+                    setMediaSession(session.sessionToken)
+                    setRemotePlaybackInfo("Remote device", 0, null)
+                })
             }
             build()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 3d59497..d912a89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -30,6 +30,8 @@
 import com.android.settingslib.media.MediaDevice
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 
@@ -44,6 +46,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
@@ -71,6 +74,8 @@
     @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
     @Mock private lateinit var lmm: LocalMediaManager
     @Mock private lateinit var mr2: MediaRouter2Manager
+    @Mock private lateinit var muteAwaitFactory: MediaMuteAwaitConnectionManagerFactory
+    @Mock private lateinit var muteAwaitManager: MediaMuteAwaitConnectionManager
     private lateinit var fakeFgExecutor: FakeExecutor
     private lateinit var fakeBgExecutor: FakeExecutor
     @Mock private lateinit var dumpster: DumpManager
@@ -88,14 +93,22 @@
     fun setUp() {
         fakeFgExecutor = FakeExecutor(FakeSystemClock())
         fakeBgExecutor = FakeExecutor(FakeSystemClock())
-        manager = MediaDeviceManager(controllerFactory, lmmFactory, mr2, fakeFgExecutor,
-                fakeBgExecutor, dumpster)
+        manager = MediaDeviceManager(
+                controllerFactory,
+                lmmFactory,
+                mr2,
+                muteAwaitFactory,
+                fakeFgExecutor,
+                fakeBgExecutor,
+                dumpster
+        )
         manager.addListener(listener)
 
         // Configure mocks.
         whenever(device.name).thenReturn(DEVICE_NAME)
         whenever(device.iconWithoutBackground).thenReturn(icon)
         whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm)
+        whenever(muteAwaitFactory.create(lmm)).thenReturn(muteAwaitManager)
         whenever(lmm.getCurrentConnectedDevice()).thenReturn(device)
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
 
@@ -146,6 +159,7 @@
         manager.onMediaDataRemoved(KEY)
         fakeBgExecutor.runAllReady()
         verify(lmm).unregisterCallback(any())
+        verify(muteAwaitManager).stopListening()
     }
 
     @Test
@@ -169,6 +183,7 @@
         fakeFgExecutor.runAllReady()
         // THEN the listener for the old key should removed.
         verify(lmm).unregisterCallback(any())
+        verify(muteAwaitManager).stopListening()
         // AND a new device event emitted
         val data = captureDeviceData(KEY, KEY_OLD)
         assertThat(data.enabled).isTrue()
@@ -240,6 +255,7 @@
         manager.onMediaDataLoaded(KEY, null, mediaData)
         fakeBgExecutor.runAllReady()
         val deviceCallback = captureCallback()
+        verify(muteAwaitManager).startListening()
         // WHEN the device list changes
         deviceCallback.onDeviceListUpdate(mutableListOf(device))
         assertThat(fakeBgExecutor.runAllReady()).isEqualTo(1)
@@ -268,6 +284,51 @@
     }
 
     @Test
+    fun onAboutToConnectDeviceChangedWithNonNullParams() {
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        // Run and reset the executors and listeners so we only focus on new events.
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(listener)
+
+        val deviceCallback = captureCallback()
+        // WHEN the about-to-connect device changes to non-null
+        val name = "AboutToConnectDeviceName"
+        val mockIcon = mock(Drawable::class.java)
+        deviceCallback.onAboutToConnectDeviceChanged(name, mockIcon)
+        assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+        // THEN the about-to-connect device is returned
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(name)
+        assertThat(data.icon).isEqualTo(mockIcon)
+    }
+
+    @Test
+    fun onAboutToConnectDeviceChangedWithNullParams() {
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        val deviceCallback = captureCallback()
+        // First set a non-null about-to-connect device
+        deviceCallback.onAboutToConnectDeviceChanged(
+            "AboutToConnectDeviceName", mock(Drawable::class.java)
+        )
+        // Run and reset the executors and listeners so we only focus on new events.
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(listener)
+
+        // WHEN the about-to-connect device changes to null
+        deviceCallback.onAboutToConnectDeviceChanged(null, null)
+        assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+        // THEN the normal device is returned
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(DEVICE_NAME)
+        assertThat(data.icon).isEqualTo(icon)
+    }
+
+    @Test
     fun listenerReceivesKeyRemoved() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
         // WHEN the notification is removed
@@ -376,6 +437,24 @@
         verify(mr2, never()).getRoutingSessionForMediaController(eq(controller))
     }
 
+    @Test
+    fun testRemotePlaybackDeviceOverride() {
+        whenever(route.name).thenReturn(DEVICE_NAME)
+        val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null)
+        val mediaDataWithDevice = mediaData.copy(device = deviceData)
+
+        // GIVEN media data that already has a device set
+        manager.onMediaDataLoaded(KEY, null, mediaDataWithDevice)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+
+        // THEN we keep the device info, and don't register a listener
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isFalse()
+        assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
+        verify(lmm, never()).registerCallback(any())
+    }
+
     fun captureCallback(): LocalMediaManager.DeviceCallback {
         val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
         verify(lmm).registerCallback(captor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 0576987..bdc3117 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -201,7 +201,7 @@
 
         assertThat(devices.containsAll(mMediaDevices)).isTrue();
         assertThat(devices.size()).isEqualTo(mMediaDevices.size());
-        verify(mCb).onRouteChanged();
+        verify(mCb).onDeviceListChanged();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
new file mode 100644
index 0000000..88c4514
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.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.systemui.media.muteawait
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.media.AudioAttributes.USAGE_MEDIA
+import android.media.AudioAttributes.USAGE_UNKNOWN
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.settingslib.media.DeviceIconUtil
+import com.android.settingslib.media.LocalMediaManager
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+
+@SmallTest
+class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
+    private lateinit var muteAwaitConnectionManager: MediaMuteAwaitConnectionManager
+    @Mock
+    private lateinit var audioManager: AudioManager
+    @Mock
+    private lateinit var deviceIconUtil: DeviceIconUtil
+    @Mock
+    private lateinit var localMediaManager: LocalMediaManager
+    private lateinit var icon: Drawable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        context.addMockSystemService(Context.AUDIO_SERVICE, audioManager)
+        icon = context.getDrawable(R.drawable.ic_cake)!!
+        whenever(deviceIconUtil.getIconFromAudioDeviceType(any(), any())).thenReturn(icon)
+
+        muteAwaitConnectionManager = MediaMuteAwaitConnectionManager(
+            FakeExecutor(FakeSystemClock()),
+            localMediaManager,
+            context,
+            deviceIconUtil
+        )
+    }
+
+    @Test
+    fun constructor_audioManagerCallbackNotRegistered() {
+        verify(audioManager, never()).registerMuteAwaitConnectionCallback(any(), any())
+    }
+
+    @Test
+    fun startListening_audioManagerCallbackRegistered() {
+        muteAwaitConnectionManager.startListening()
+
+        verify(audioManager).registerMuteAwaitConnectionCallback(any(), any())
+    }
+
+    @Test
+    fun stopListening_audioManagerCallbackUnregistered() {
+        muteAwaitConnectionManager.stopListening()
+
+        verify(audioManager).unregisterMuteAwaitConnectionCallback(any())
+    }
+
+    @Test
+    fun startListening_audioManagerHasNoMuteAwaitDevice_localMediaMangerNotNotified() {
+        whenever(audioManager.mutingExpectedDevice).thenReturn(null)
+
+        muteAwaitConnectionManager.startListening()
+
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+    }
+
+    @Test
+    fun startListening_audioManagerHasMuteAwaitDevice_localMediaMangerNotified() {
+        whenever(audioManager.mutingExpectedDevice).thenReturn(DEVICE)
+
+        muteAwaitConnectionManager.startListening()
+
+        verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+    }
+
+    @Test
+    fun onMutedUntilConnection_notUsageMedia_localMediaManagerNotNotified() {
+        muteAwaitConnectionManager.startListening()
+        val muteAwaitListener = getMuteAwaitListener()
+
+        muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN))
+
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+    }
+
+    @Test
+    fun onMutedUntilConnection_isUsageMedia_localMediaManagerNotified() {
+        muteAwaitConnectionManager.startListening()
+        val muteAwaitListener = getMuteAwaitListener()
+
+
+        muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
+
+        verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+    }
+
+    @Test
+    fun onUnmutedEvent_noDeviceMutedBefore_localMediaManagerNotNotified() {
+        muteAwaitConnectionManager.startListening()
+        val muteAwaitListener = getMuteAwaitListener()
+
+        muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
+
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+    }
+
+    @Test
+    fun onUnmutedEvent_notSameDevice_localMediaManagerNotNotified() {
+        muteAwaitConnectionManager.startListening()
+        val muteAwaitListener = getMuteAwaitListener()
+        muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
+        reset(localMediaManager)
+
+        val otherDevice = AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_USB_HEADSET,
+                "address",
+                "DifferentName",
+                listOf(),
+                listOf(),
+        )
+        muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, otherDevice, intArrayOf(USAGE_MEDIA))
+
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+    }
+
+    @Test
+    fun onUnmutedEvent_notUsageMedia_localMediaManagerNotNotified() {
+        muteAwaitConnectionManager.startListening()
+        val muteAwaitListener = getMuteAwaitListener()
+        muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
+        reset(localMediaManager)
+
+        muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_UNKNOWN))
+
+        verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+    }
+
+    @Test
+    fun onUnmutedEvent_sameDeviceAndUsageMedia_localMediaManagerNotified() {
+        muteAwaitConnectionManager.startListening()
+        val muteAwaitListener = getMuteAwaitListener()
+        muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
+        reset(localMediaManager)
+
+        muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
+
+        verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null))
+    }
+
+    private fun getMuteAwaitListener(): AudioManager.MuteAwaitConnectionCallback {
+        val listenerCaptor = ArgumentCaptor.forClass(
+                AudioManager.MuteAwaitConnectionCallback::class.java
+        )
+        verify(audioManager).registerMuteAwaitConnectionCallback(any(), listenerCaptor.capture())
+        return listenerCaptor.value!!
+    }
+}
+
+private const val DEVICE_NAME = "DeviceName"
+private val DEVICE = AudioDeviceAttributes(
+        AudioDeviceAttributes.ROLE_OUTPUT,
+        AudioDeviceInfo.TYPE_USB_HEADSET,
+        "address",
+        DEVICE_NAME,
+        listOf(),
+        listOf(),
+)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index cb05d03..14afece 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -203,7 +203,9 @@
 
         verify(statusBarManager).updateMediaTapToTransferReceiverDisplay(
             eq(StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER),
-            any()
+            any(),
+            nullable(),
+            nullable()
         )
     }
 
@@ -213,7 +215,9 @@
 
         verify(statusBarManager).updateMediaTapToTransferReceiverDisplay(
             eq(StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER),
-            any()
+            any(),
+            nullable(),
+            nullable()
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 242fd19..f05d621 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -92,16 +92,16 @@
     fun setIcon_viewHasIconAndContentDescription() {
         controllerCommon.displayChip(getState())
         val chipView = getChipView()
-        val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
-        val contentDescription = "test description"
 
-        controllerCommon.setIcon(MediaTttChipState(drawable, contentDescription), chipView)
+        val state = MediaTttChipState(PACKAGE_NAME)
+        controllerCommon.setIcon(state, chipView)
 
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(drawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(contentDescription)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+        assertThat(chipView.getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
     }
 
-    private fun getState() = MediaTttChipState(appIconDrawable, APP_ICON_CONTENT_DESCRIPTION)
+    private fun getState() = MediaTttChipState(PACKAGE_NAME)
 
     private fun getChipView(): ViewGroup {
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -122,4 +122,4 @@
     }
 }
 
-private const val APP_ICON_CONTENT_DESCRIPTION = "Content description"
+private const val PACKAGE_NAME = "com.android.systemui"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index fce4954..44f691c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -19,6 +19,7 @@
 import android.app.StatusBarManager
 import android.graphics.drawable.Icon
 import android.media.MediaRoute2Info
+import android.os.Handler
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
@@ -52,7 +53,8 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        controllerReceiver = MediaTttChipControllerReceiver(commandQueue, context, windowManager)
+        controllerReceiver = MediaTttChipControllerReceiver(
+                commandQueue, context, windowManager, Handler.getMain())
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
         verify(commandQueue).addCallback(callbackCaptor.capture())
@@ -61,19 +63,24 @@
 
     @Test
     fun commandQueueCallback_closeToSender_triggersChip() {
+        val appName = "FakeAppName"
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
-            routeInfo
+            routeInfo,
+            /* appIcon= */ null,
+            appName
         )
 
-        assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(ROUTE_NAME)
+        assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName)
     }
 
     @Test
     fun commandQueueCallback_farFromSender_noChipShown() {
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
-            routeInfo
+            routeInfo,
+            null,
+            null
         )
 
         verify(windowManager, never()).addView(any(), any())
@@ -83,12 +90,16 @@
     fun commandQueueCallback_closeThenFar_chipShownThenHidden() {
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
-            routeInfo
+            routeInfo,
+            null,
+            null
         )
 
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
-            routeInfo
+            routeInfo,
+            null,
+            null
         )
 
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -97,14 +108,43 @@
     }
 
     @Test
-    fun displayChip_chipContainsIcon() {
-        val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
-        val contentDescription = "Test description"
+    fun displayChip_nullAppIconDrawable_iconIsFromPackageName() {
+        val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, "appName")
 
-        controllerReceiver.displayChip(ChipStateReceiver(drawable, contentDescription))
+        controllerReceiver.displayChip(state)
+
+        assertThat(getChipView().getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+
+    }
+
+    @Test
+    fun displayChip_hasAppIconDrawable_iconIsDrawable() {
+        val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
+        val state = ChipStateReceiver(PACKAGE_NAME, drawable, "appName")
+
+        controllerReceiver.displayChip(state)
 
         assertThat(getChipView().getAppIconView().drawable).isEqualTo(drawable)
-        assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(contentDescription)
+    }
+
+    @Test
+    fun displayChip_nullAppName_iconContentDescriptionIsFromPackageName() {
+        val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName = null)
+
+        controllerReceiver.displayChip(state)
+
+        assertThat(getChipView().getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
+    }
+
+    @Test
+    fun displayChip_hasAppName_iconContentDescriptionIsAppNameOverride() {
+        val appName = "FakeAppName"
+        val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName)
+
+        controllerReceiver.displayChip(state)
+
+        assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName)
     }
 
     private fun getChipView(): ViewGroup {
@@ -116,7 +156,9 @@
     private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
 }
 
-private const val ROUTE_NAME = "Test name"
-private val routeInfo = MediaRoute2Info.Builder("id", ROUTE_NAME)
+private const val PACKAGE_NAME = "com.android.systemui"
+
+private val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
     .addFeature("feature")
+    .setPackageName(PACKAGE_NAME)
     .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index c74ac64..dc39893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.media.taptotransfer.sender
 
 import android.app.StatusBarManager
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
 import android.media.MediaRoute2Info
 import android.view.View
 import android.view.WindowManager
@@ -44,8 +42,6 @@
 @SmallTest
 @Ignore("b/216286227")
 class MediaTttChipControllerSenderTest : SysuiTestCase() {
-    private lateinit var appIconDrawable: Drawable
-
     private lateinit var controllerSender: MediaTttChipControllerSender
 
     @Mock
@@ -57,7 +53,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
         controllerSender = MediaTttChipControllerSender(commandQueue, context, windowManager)
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -197,8 +192,9 @@
         controllerSender.displayChip(state)
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+        assertThat(chipView.getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
         assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -211,8 +207,9 @@
         controllerSender.displayChip(state)
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+        assertThat(chipView.getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
         assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -225,8 +222,9 @@
         controllerSender.displayChip(state)
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+        assertThat(chipView.getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
         assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -239,8 +237,9 @@
         controllerSender.displayChip(state)
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+        assertThat(chipView.getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
         assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -253,8 +252,9 @@
         controllerSender.displayChip(state)
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+        assertThat(chipView.getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
         assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
         assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
@@ -314,8 +314,9 @@
         controllerSender.displayChip(state)
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+        assertThat(chipView.getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
         assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
         assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
@@ -375,8 +376,9 @@
         controllerSender.displayChip(state)
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
-        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+        assertThat(chipView.getAppIconView().contentDescription)
+                .isEqualTo(state.getAppName(context))
         assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -449,39 +451,40 @@
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun almostCloseToStartCast() =
-        AlmostCloseToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+        AlmostCloseToStartCast(PACKAGE_NAME, DEVICE_NAME)
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun almostCloseToEndCast() =
-        AlmostCloseToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+        AlmostCloseToEndCast(PACKAGE_NAME, DEVICE_NAME)
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun transferToReceiverTriggered() =
-        TransferToReceiverTriggered(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+        TransferToReceiverTriggered(PACKAGE_NAME, DEVICE_NAME)
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun transferToThisDeviceTriggered() =
-        TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC)
+        TransferToThisDeviceTriggered(PACKAGE_NAME)
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
         TransferToReceiverSucceeded(
-            appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
+            PACKAGE_NAME, DEVICE_NAME, undoCallback
         )
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
         TransferToThisDeviceSucceeded(
-            appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
+            PACKAGE_NAME, DEVICE_NAME, undoCallback
         )
 
     /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC)
+    private fun transferFailed() = TransferFailed(PACKAGE_NAME)
 }
 
 private const val DEVICE_NAME = "My Tablet"
-private const val APP_ICON_CONTENT_DESC = "Content description"
+private const val PACKAGE_NAME = "com.android.systemui"
 
 private val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
     .addFeature("feature")
+    .setPackageName(PACKAGE_NAME)
     .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
deleted file mode 100644
index 84776c7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.testing.ViewUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.DetailAdapter;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class QSDetailTest extends SysuiTestCase {
-
-    private MetricsLogger mMetricsLogger;
-    private QSDetail mQsDetail;
-    private QSPanelController mQsPanelController;
-    private QuickStatusBarHeader mQuickHeader;
-    private ActivityStarter mActivityStarter;
-    private DetailAdapter mMockDetailAdapter;
-    private TestableLooper mTestableLooper;
-    private UiEventLoggerFake mUiEventLogger;
-    private FrameLayout mParent;
-
-    @Before
-    public void setup() throws Exception {
-        mTestableLooper = TestableLooper.get(this);
-        mUiEventLogger = QSEvents.INSTANCE.setLoggerForTesting();
-
-        mParent = new FrameLayout(mContext);
-        mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
-        mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
-        LayoutInflater.from(mContext).inflate(R.layout.qs_detail, mParent);
-        mQsDetail = (QSDetail) mParent.getChildAt(0);
-
-        mQsPanelController = mock(QSPanelController.class);
-        mQuickHeader = mock(QuickStatusBarHeader.class);
-        mQsDetail.setQsPanel(mQsPanelController, mQuickHeader, mock(QSFooter.class),
-                mock(FalsingManager.class));
-        mQsDetail.mClipper = mock(QSDetailClipper.class);
-
-        mMockDetailAdapter = mock(DetailAdapter.class);
-        when(mMockDetailAdapter.createDetailView(any(), any(), any()))
-                .thenReturn(new View(mContext));
-
-        // Only detail in use is the user detail
-        when(mMockDetailAdapter.openDetailEvent())
-                .thenReturn(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN);
-        when(mMockDetailAdapter.closeDetailEvent())
-                .thenReturn(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE);
-        when(mMockDetailAdapter.moreSettingsEvent())
-                .thenReturn(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS);
-        ViewUtils.attachView(mParent);
-    }
-
-    @After
-    public void tearDown() {
-        QSEvents.INSTANCE.resetLogger();
-        mTestableLooper.processAllMessages();
-        ViewUtils.detachView(mParent);
-    }
-
-    @Test
-    public void testShowDetail_Metrics() {
-        mTestableLooper.processAllMessages();
-
-        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
-        verify(mMetricsLogger).visible(eq(mMockDetailAdapter.getMetricsCategory()));
-        assertEquals(1, mUiEventLogger.numLogs());
-        assertEquals(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN.getId(), mUiEventLogger.eventId(0));
-        mUiEventLogger.getLogs().clear();
-
-        mQsDetail.handleShowingDetail(null, 0, 0, false);
-        verify(mMetricsLogger).hidden(eq(mMockDetailAdapter.getMetricsCategory()));
-
-        assertEquals(1, mUiEventLogger.numLogs());
-        assertEquals(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE.getId(), mUiEventLogger.eventId(0));
-    }
-
-    @Test
-    public void testShowDetail_ShouldAnimate() {
-        mTestableLooper.processAllMessages();
-
-        when(mMockDetailAdapter.shouldAnimate()).thenReturn(true);
-        mQsDetail.setFullyExpanded(true);
-
-        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
-        verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(),
-                eq(true) /* in */, any());
-        clearInvocations(mQsDetail.mClipper);
-
-        mQsDetail.handleShowingDetail(null, 0, 0, false);
-        verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(),
-                eq(false) /* in */, any());
-    }
-
-    @Test
-    public void testShowDetail_ShouldNotAnimate() {
-        mTestableLooper.processAllMessages();
-
-        when(mMockDetailAdapter.shouldAnimate()).thenReturn(false);
-        mQsDetail.setFullyExpanded(true);
-
-        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
-        verify(mQsDetail.mClipper).updateCircularClip(eq(false) /* animate */, anyInt(), anyInt(),
-                eq(true) /* in */, any());
-        clearInvocations(mQsDetail.mClipper);
-
-        // Detail adapters should always animate on close. shouldAnimate() should only affect the
-        // open transition
-        mQsDetail.handleShowingDetail(null, 0, 0, false);
-        verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(),
-                eq(false) /* in */, any());
-    }
-
-    @Test
-    public void testDoneButton_CloseDetailPanel() {
-        mTestableLooper.processAllMessages();
-
-        when(mMockDetailAdapter.onDoneButtonClicked()).thenReturn(false);
-
-        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
-        mQsDetail.requireViewById(android.R.id.button1).performClick();
-        verify(mQsPanelController).closeDetail();
-    }
-
-    @Test
-    public void testDoneButton_KeepDetailPanelOpen() {
-        mTestableLooper.processAllMessages();
-
-        when(mMockDetailAdapter.onDoneButtonClicked()).thenReturn(true);
-
-        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
-        mQsDetail.requireViewById(android.R.id.button1).performClick();
-        verify(mQsPanelController, never()).closeDetail();
-    }
-
-    @Test
-    public void testMoreSettingsButton() {
-        mTestableLooper.processAllMessages();
-
-        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
-        mUiEventLogger.getLogs().clear();
-        mQsDetail.requireViewById(android.R.id.button2).performClick();
-
-        int metricsCategory = mMockDetailAdapter.getMetricsCategory();
-        verify(mMetricsLogger).action(eq(ACTION_QS_MORE_SETTINGS), eq(metricsCategory));
-        assertEquals(1, mUiEventLogger.numLogs());
-        assertEquals(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS.getId(), mUiEventLogger.eventId(0));
-
-        verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt());
-    }
-
-    @Test
-    public void testNullAdapterClick() {
-        DetailAdapter mock = mock(DetailAdapter.class);
-        when(mock.moreSettingsEvent()).thenReturn(DetailAdapter.INVALID);
-        mQsDetail.setupDetailFooter(mock);
-        mQsDetail.requireViewById(android.R.id.button2).performClick();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 3266d6a..4ab3926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -146,7 +146,6 @@
                 mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class),
                 mock(SecureSettings.class), mock(CustomTileStatePersister.class),
                 mTileServiceRequestControllerBuilder, mock(TileLifecycleManager.Factory.class));
-        qs.setHost(host);
 
         qs.setListening(true);
         processAllMessages();
@@ -186,7 +185,6 @@
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
                 commandQueue,
-                new QSDetailDisplayer(),
                 mQSMediaHost,
                 mQQSMediaHost,
                 mBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
deleted file mode 100644
index b2ca62f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaHost;
-import com.android.systemui.plugins.qs.QSTileView;
-import com.android.systemui.qs.customize.QSCustomizerController;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.settings.brightness.BrightnessController;
-import com.android.systemui.settings.brightness.BrightnessSliderController;
-import com.android.systemui.settings.brightness.ToggleSlider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.animation.DisappearParameters;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Collections;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class QSPanelControllerTest extends SysuiTestCase {
-
-    @Mock
-    private QSPanel mQSPanel;
-    @Mock
-    private QSTileHost mQSTileHost;
-    @Mock
-    private QSCustomizerController mQSCustomizerController;
-    @Mock
-    private QSTileRevealController.Factory mQSTileRevealControllerFactory;
-    @Mock
-    private QSTileRevealController mQSTileRevealController;
-    @Mock
-    private MediaHost mMediaHost;
-    @Mock
-    private MetricsLogger mMetricsLogger;
-    private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
-    private DumpManager mDumpManager = new DumpManager();
-    @Mock
-    private TunerService mTunerService;
-    @Mock
-    private QSFgsManagerFooter mQSFgsManagerFooter;
-    @Mock
-    private QSSecurityFooter mQSSecurityFooter;
-    @Mock
-    private QSLogger mQSLogger;
-    @Mock
-    private BrightnessController.Factory mBrightnessControllerFactory;
-    @Mock
-    private BrightnessController mBrightnessController;
-    @Mock
-    private BrightnessSliderController.Factory mToggleSliderViewControllerFactory;
-    @Mock
-    private BrightnessSliderController mBrightnessSliderController;
-    @Mock
-    QSTileImpl mQSTile;
-    @Mock
-    QSTileView mQSTileView;
-    @Mock
-    PagedTileLayout mPagedTileLayout;
-    @Mock
-    CommandQueue mCommandQueue;
-    FalsingManagerFake mFalsingManager = new FalsingManagerFake();
-    @Mock
-    Resources mResources;
-    @Mock
-    Configuration mConfiguration;
-    @Mock
-    FeatureFlags mFeatureFlags;
-
-    private QSPanelController mController;
-
-    @Before
-    public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        when(mQSPanel.isAttachedToWindow()).thenReturn(true);
-        when(mQSPanel.getDumpableTag()).thenReturn("QSPanel");
-        when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout);
-        when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
-        when(mQSPanel.getResources()).thenReturn(mResources);
-        when(mResources.getConfiguration()).thenReturn(mConfiguration);
-        when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
-        when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
-        when(mToggleSliderViewControllerFactory.create(any(), any()))
-                .thenReturn(mBrightnessSliderController);
-        when(mBrightnessControllerFactory.create(any(ToggleSlider.class)))
-                .thenReturn(mBrightnessController);
-        when(mQSTileRevealControllerFactory.create(any(), any()))
-                .thenReturn(mQSTileRevealController);
-        when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
-
-        mController = new QSPanelController(mQSPanel, mQSFgsManagerFooter, mQSSecurityFooter,
-                mTunerService, mQSTileHost, mQSCustomizerController, true, mMediaHost,
-                mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
-                mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
-                mFalsingManager, mCommandQueue, mFeatureFlags
-        );
-
-        mController.init();
-    }
-
-    @Test
-    public void testOpenDetailsWithNonExistingTile_NoException() {
-        mController.openDetails("none");
-
-        verify(mQSPanel, never()).openDetails(any());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 4ae19332..5213a30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -36,10 +36,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
 
@@ -64,9 +61,6 @@
     private lateinit var mParentView: ViewGroup
 
     @Mock
-    private lateinit var mCallback: QSDetail.Callback
-
-    @Mock
     private lateinit var mQSTileView: QSTileView
 
     private lateinit var mFooter: View
@@ -97,29 +91,10 @@
             whenever(mHost.tiles).thenReturn(emptyList())
             whenever(mHost.createTileView(any(), any(), anyBoolean())).thenReturn(mQSTileView)
             mQsPanel.addTile(mDndTileRecord)
-            mQsPanel.setCallback(mCallback)
         }
     }
 
     @Test
-    fun testOpenDetailsWithExistingTile_NoException() {
-        mTestableLooper.runWithLooper {
-            mQsPanel.openDetails(dndTile)
-        }
-
-        verify(mCallback).onShowingDetail(any(), anyInt(), anyInt())
-    }
-
-    @Test
-    fun testOpenDetailsWithNullParameter_NoException() {
-        mTestableLooper.runWithLooper {
-            mQsPanel.openDetails(null)
-        }
-
-        verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt())
-    }
-
-    @Test
     fun testSecurityFooter_appearsOnBottomOnSplitShade() {
         mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
         mQsPanel.switchSecurityFooter(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 8b7346d..30b464b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -53,7 +53,6 @@
 import com.android.internal.logging.InstanceId;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSTileHost;
@@ -420,11 +419,5 @@
 
         @Override
         public void destroy() {}
-
-
-        @Override
-        public DetailAdapter getDetailAdapter() {
-            return null;
-        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
index a1c60a6..bdfbca4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -20,6 +20,7 @@
 import android.content.ComponentName
 import android.content.DialogInterface
 import android.graphics.drawable.Icon
+import android.os.RemoteException
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
@@ -275,11 +276,89 @@
         assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
     }
 
+    @Test
+    fun interfaceThrowsRemoteException_doesntCrash() {
+        val cancelListenerCaptor =
+                ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+        val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+        verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
+
+        val callback = object : IAddTileResultCallback.Stub() {
+            override fun onTileRequest(p0: Int) {
+                throw RemoteException()
+            }
+        }
+        captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+
+        cancelListenerCaptor.value.onCancel(tileRequestDialog)
+    }
+
+    @Test
+    fun testDismissDialogResponse() {
+        val dismissListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
+    }
+
+    @Test
+    fun addTileAndThenDismissSendsOnlyAddTile() {
+        // After clicking, the dialog is dismissed. This tests that only one response
+        // is sent (the first one)
+        val dismissListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+        val clickListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
+        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
+        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
+        assertThat(callback.timesCalled).isEqualTo(1)
+    }
+
+    @Test
+    fun cancelAndThenDismissSendsOnlyOnce() {
+        // After cancelling, the dialog is dismissed. This tests that only one response
+        // is sent.
+        val dismissListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+        val cancelListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+        cancelListenerCaptor.value.onCancel(tileRequestDialog)
+        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
+        assertThat(callback.timesCalled).isEqualTo(1)
+    }
+
     private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> {
         var lastAccepted: Int? = null
             private set
+
+        var timesCalled = 0
+            private set
+
         override fun accept(t: Int) {
             lastAccepted = t
+            timesCalled++
         }
 
         override fun onTileRequest(r: Int) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 88b133e..0f2c2647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -48,7 +48,6 @@
 import com.android.systemui.qs.tiles.RotationLockTile
 import com.android.systemui.qs.tiles.ScreenRecordTile
 import com.android.systemui.qs.tiles.UiModeNightTile
-import com.android.systemui.qs.tiles.UserTile
 import com.android.systemui.qs.tiles.WifiTile
 import com.android.systemui.qs.tiles.WorkModeTile
 import com.android.systemui.util.leak.GarbageMonitor
@@ -76,7 +75,6 @@
         "location" to LocationTile::class.java,
         "cast" to CastTile::class.java,
         "hotspot" to HotspotTile::class.java,
-        "user" to UserTile::class.java,
         "battery" to BatterySaverTile::class.java,
         "saver" to DataSaverTile::class.java,
         "night" to NightDisplayTile::class.java,
@@ -115,7 +113,6 @@
     @Mock private lateinit var locationTile: LocationTile
     @Mock private lateinit var castTile: CastTile
     @Mock private lateinit var hotspotTile: HotspotTile
-    @Mock private lateinit var userTile: UserTile
     @Mock private lateinit var batterySaverTile: BatterySaverTile
     @Mock private lateinit var dataSaverTile: DataSaverTile
     @Mock private lateinit var nightDisplayTile: NightDisplayTile
@@ -159,7 +156,6 @@
                 { locationTile },
                 { castTile },
                 { hotspotTile },
-                { userTile },
                 { batterySaverTile },
                 { dataSaverTile },
                 { nightDisplayTile },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 3a3d154..9b0142d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -147,5 +147,6 @@
                     current,
                     false /* isAddUser */,
                     false /* isRestricted */,
-                    true /* isSwitchToEnabled */)
+                    true /* isSwitchToEnabled */,
+                    false /* isAddSupervisedUser */)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index b7fdc1a..8695b29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -22,11 +22,13 @@
 import android.testing.AndroidTestingRunner
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PseudoGridView
+import com.android.systemui.qs.QSUserSwitcherEvent
 import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.mockito.any
@@ -62,6 +64,8 @@
     private lateinit var launchView: View
     @Mock
     private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock
+    private lateinit var uiEventLogger: UiEventLogger
     @Captor
     private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener>
 
@@ -79,6 +83,7 @@
                 activityStarter,
                 falsingManager,
                 dialogLaunchAnimator,
+                uiEventLogger,
                 { dialog }
         )
     }
@@ -87,6 +92,7 @@
     fun showDialog_callsDialogShow() {
         controller.showDialog(launchView)
         verify(dialogLaunchAnimator).showFromView(dialog, launchView)
+        verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
     }
 
     @Test
@@ -108,10 +114,14 @@
     }
 
     @Test
-    fun doneButtonSetWithNullHandler() {
+    fun doneButtonLogsCorrectly() {
         controller.showDialog(launchView)
 
-        verify(dialog).setPositiveButton(anyInt(), eq(null))
+        verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor))
+
+        clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
+
+        verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
     }
 
     @Test
@@ -129,6 +139,7 @@
                         argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
                         eq(0)
                 )
+        verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
index 218e7db..711187b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
@@ -22,7 +22,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.annotations.Requires;
-import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QS.HeightListener;
 import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
@@ -99,7 +98,6 @@
 
     @Requires(target = QS.class, version = QS.VERSION)
     @Requires(target = HeightListener.class, version = HeightListener.VERSION)
-    @Requires(target = DetailAdapter.class, version = DetailAdapter.VERSION)
     public static class QSImpl {
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 5924329..466d954 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -244,6 +244,9 @@
         verify(mKeyguardStateController).addCallback(
                 mKeyguardStateControllerCallbackCaptor.capture());
         mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+
+        mExecutor.runAllReady();
+        reset(mRotateTextViewController);
     }
 
     @Test
@@ -328,6 +331,7 @@
     @Test
     public void disclosure_unmanaged() {
         createController();
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
         when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
         sendUpdateDisclosureBroadcast();
@@ -339,6 +343,7 @@
     @Test
     public void disclosure_deviceOwner_noOrganizationName() {
         createController();
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
         sendUpdateDisclosureBroadcast();
@@ -350,6 +355,7 @@
     @Test
     public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() {
         createController();
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
         when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
                 new UserInfo(10, /* name */ null, /* flags */ FLAG_MANAGED_PROFILE)));
@@ -363,6 +369,7 @@
     @Test
     public void disclosure_deviceOwner_withOrganizationName() {
         createController();
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
         sendUpdateDisclosureBroadcast();
@@ -374,6 +381,7 @@
     @Test
     public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() {
         createController();
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
         when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
                 new UserInfo(10, /* name */ null, FLAG_MANAGED_PROFILE)));
@@ -386,6 +394,7 @@
 
     @Test
     public void disclosure_updateOnTheFly() {
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
         createController();
 
@@ -416,6 +425,7 @@
     public void disclosure_deviceOwner_financedDeviceWithOrganizationName() {
         createController();
 
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
         when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
@@ -738,7 +748,8 @@
     public void testEmptyOwnerInfoHidesIndicationArea() {
         createController();
 
-        // GIVEN the owner info is set to an empty string
+        // GIVEN the owner info is set to an empty string & keyguard is showing
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn("");
 
         // WHEN asked to update the indication area
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 8c5f04f..5e11858 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -37,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -55,6 +56,7 @@
 
     @Mock private NotificationHandler mNotificationHandler;
     @Mock private NotificationManager mNotificationManager;
+    @Mock private PluginManager mPluginManager;
 
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -70,7 +72,8 @@
                 mContext,
                 mNotificationManager,
                 mFakeSystemClock,
-                mFakeExecutor);
+                mFakeExecutor,
+                mPluginManager);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 new Notification(), UserHandle.CURRENT, null, 0);
 
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 fb232ba..ed144fa 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
@@ -126,10 +126,9 @@
                 .thenReturn(true);
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
         when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
-        mContext.addMockSystemService(PowerManager.class, mPowerManager);
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
         res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
-        mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
+        mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
                 mKeyguardViewMediator, mScrimController, mShadeController,
                 mNotificationShadeWindowController, mKeyguardStateController, mHandler,
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index e9590b0..ed22cd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Assert;
@@ -47,6 +48,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Optional;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -76,7 +79,6 @@
                 mDependency,
                 TestableLooper.get(this));
         mFirst = testHelper.createRow();
-        mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher);
         mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
                 mock(TextView.class));
         mHeadsUpManager = mock(HeadsUpManagerPhone.class);
@@ -92,13 +94,14 @@
                 mStatusbarStateController,
                 mBypassController,
                 mWakeUpCoordinator,
+                mDarkIconDispatcher,
                 mKeyguardStateController,
                 mCommandQueue,
                 mStackScrollerController,
                 mPanelView,
                 mHeadsUpStatusBarView,
-                new View(mContext),
-                mOperatorNameView);
+                new Clock(mContext, null),
+                Optional.of(mOperatorNameView));
         mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f);
     }
 
@@ -173,13 +176,14 @@
                 mStatusbarStateController,
                 mBypassController,
                 mWakeUpCoordinator,
+                mDarkIconDispatcher,
                 mKeyguardStateController,
                 mCommandQueue,
                 mStackScrollerController,
                 mPanelView,
                 mHeadsUpStatusBarView,
-                new View(mContext),
-                new View(mContext));
+                new Clock(mContext, null),
+                Optional.empty());
 
         Assert.assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
         Assert.assertEquals(appearFraction, newController.mAppearFraction, 0.0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 7e33c01..cc4abfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -20,6 +20,8 @@
 
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 
+import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -41,6 +43,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -91,7 +96,9 @@
         mLightBarController.onStatusBarAppearanceChanged(
                 appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
                 false /* navbarColorManagedByIme */);
-        verify(mStatusBarIconController).setIconsDarkArea(eq(firstBounds));
+        ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
+        verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
+        assertTrue(captor.getValue().contains(firstBounds));
         verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
     }
 
@@ -106,7 +113,29 @@
         mLightBarController.onStatusBarAppearanceChanged(
                 appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
                 false /* navbarColorManagedByIme */);
-        verify(mStatusBarIconController).setIconsDarkArea(eq(secondBounds));
+        ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
+        verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
+        assertTrue(captor.getValue().contains(secondBounds));
+        verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
+    }
+
+    @Test
+    public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackLightMultipleStackDark() {
+        final Rect firstBounds = new Rect(0, 0, 1, 1);
+        final Rect secondBounds = new Rect(1, 0, 2, 1);
+        final Rect thirdBounds = new Rect(2, 0, 3, 1);
+        final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+                new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds),
+                new AppearanceRegion(0 /* appearance */, secondBounds),
+                new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
+        };
+        mLightBarController.onStatusBarAppearanceChanged(
+                appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
+                false /* navbarColorManagedByIme */);
+        ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
+        verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
+        assertTrue(captor.getValue().contains(firstBounds));
+        assertTrue(captor.getValue().contains(thirdBounds));
         verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index c13b335..7070bc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -78,6 +78,7 @@
     @Mock private NotificationEntryManager mNotificationEntryManager;
     @Mock private RowContentBindStage mBindStage;
     @Mock PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+    @Mock StatusBarStateController mStatusBarStateController;
     @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
     private NotificationEntryListener mNotificationEntryListener;
     private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>();
@@ -94,7 +95,7 @@
                 .thenReturn(mPendingEntries.values());
 
         mGroupManager = new NotificationGroupManagerLegacy(
-                mock(StatusBarStateController.class),
+                mStatusBarStateController,
                 () -> mPeopleNotificationIdentifier,
                 Optional.of(mock(Bubbles.class)),
                 mock(DumpManager.class));
@@ -103,7 +104,8 @@
 
         when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
 
-        mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(mBindStage);
+        mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(
+                mBindStage, mStatusBarStateController, mGroupManager);
         mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
 
         mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 7347565..f6eff82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -106,10 +106,10 @@
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -272,8 +272,6 @@
     @Mock
     private IdleHostViewController mIdleHostViewController;
     @Mock
-    private QSDetailDisplayer mQSDetailDisplayer;
-    @Mock
     private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
     @Mock
     private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
@@ -366,6 +364,8 @@
     private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock
     private NotificationShadeWindowController mNotificationShadeWindowController;
+    @Mock
+    private SysUiState mSysUiState;
     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -525,7 +525,6 @@
                 mCommunalViewComponentFactory,
                 mIdleViewComponentFactory,
                 mLockscreenShadeTransitionController,
-                mQSDetailDisplayer,
                 mGroupManager,
                 mNotificationAreaController,
                 mAuthController,
@@ -555,6 +554,7 @@
                 mControlsComponent,
                 mInteractionJankMonitor,
                 mQsFrameTranslateController,
+                mSysUiState,
                 mKeyguardUnlockAnimationController);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index a57f6a1..4a579cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -27,26 +27,22 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.CommunalStateController
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger
-import com.android.systemui.statusbar.phone.NotificationPanelViewController
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import javax.inject.Provider
 
 @SmallTest
 @TestableLooper.RunWithLooper
@@ -77,23 +73,14 @@
     private lateinit var dozeParameters: DozeParameters
 
     @Mock
-    private lateinit var userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>
-
-    @Mock
     private lateinit var screenOffAnimationController: ScreenOffAnimationController
 
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-
-    @Mock
     private lateinit var userSwitchDialogController: UserSwitchDialogController
 
     @Mock
     private lateinit var uiEventLogger: UiEventLogger
 
-    @Mock
-    private lateinit var notificationPanelViewController: NotificationPanelViewController
-
     private lateinit var view: FrameLayout
     private lateinit var testableLooper: TestableLooper
     private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
@@ -118,16 +105,12 @@
                 configurationController,
                 statusBarStateController,
                 dozeParameters,
-                userDetailViewAdapterProvider,
                 screenOffAnimationController,
-                featureFlags,
                 userSwitchDialogController,
                 uiEventLogger)
 
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
-        keyguardQsUserSwitchController
-                .setNotificationPanelViewController(notificationPanelViewController)
         `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController)
         `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true)
         keyguardQsUserSwitchController.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index e479882..0dd6cbb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -193,5 +193,6 @@
                     isCurrentUser,
                     false /* isAddUser */,
                     false /* isRestricted */,
-                    true /* isSwitchToEnabled */)
+                    true /* isSwitchToEnabled */,
+                    false /* isAddSupervisedUser */)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 9a7e702..c344aea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -54,8 +54,9 @@
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -85,7 +86,6 @@
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var activityTaskManager: IActivityTaskManager
-    @Mock private lateinit var userDetailAdapter: UserSwitcherController.UserDetailAdapter
     @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
     @Mock private lateinit var secureSettings: SecureSettings
     @Mock private lateinit var falsingManager: FalsingManager
@@ -130,6 +130,21 @@
                 .thenReturn(true)
         `when`(notificationShadeWindowView.context).thenReturn(context)
 
+        // Since userSwitcherController involves InteractionJankMonitor.
+        // Let's fulfill the dependencies.
+        val mockedContext = mock(Context::class.java)
+        doReturn(mockedContext).`when`(notificationShadeWindowView).context
+        doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
+        doNothing().`when`(threadedRenderer).addObserver(any())
+        doNothing().`when`(threadedRenderer).removeObserver(any())
+        doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
+
+        picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
+
+        setupController()
+    }
+
+    private fun setupController() {
         userSwitcherController = UserSwitcherController(
                 context,
                 activityManager,
@@ -145,7 +160,6 @@
                 falsingManager,
                 telephonyListenerManager,
                 activityTaskManager,
-                userDetailAdapter,
                 secureSettings,
                 uiBgExecutor,
                 interactionJankMonitor,
@@ -153,18 +167,6 @@
                 dumpManager,
                 dialogLaunchAnimator)
         userSwitcherController.mPauseRefreshUsers = true
-
-        // Since userSwitcherController involves InteractionJankMonitor.
-        // Let's fulfill the dependencies.
-        val mockedContext = mock(Context::class.java)
-        doReturn(mockedContext).`when`(notificationShadeWindowView).context
-        doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
-        doNothing().`when`(threadedRenderer).addObserver(any())
-        doNothing().`when`(threadedRenderer).removeObserver(any())
-        doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
-        userSwitcherController.init(notificationShadeWindowView)
-
-        picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
         userSwitcherController.init(notificationShadeWindowView)
     }
 
@@ -177,7 +179,8 @@
                 false /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(ownerId)
         `when`(userTracker.userInfo).thenReturn(ownerInfo)
 
@@ -196,7 +199,8 @@
                 false /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(ownerId)
         `when`(userTracker.userInfo).thenReturn(ownerInfo)
 
@@ -220,7 +224,8 @@
                 false /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(ownerId)
         `when`(userTracker.userInfo).thenReturn(ownerInfo)
 
@@ -240,7 +245,8 @@
                 true /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(guestInfo.id)
         `when`(userTracker.userInfo).thenReturn(guestInfo)
 
@@ -262,7 +268,8 @@
                 true /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(guestInfo.id)
         `when`(userTracker.userInfo).thenReturn(guestInfo)
 
@@ -283,7 +290,8 @@
                 true /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(guestInfo.id)
         `when`(userTracker.userInfo).thenReturn(guestInfo)
 
@@ -302,7 +310,8 @@
                 true /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(guestId)
         `when`(userTracker.userInfo).thenReturn(guestInfo)
 
@@ -323,7 +332,8 @@
                 false /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(guestId)
         `when`(userTracker.userInfo).thenReturn(guestInfo)
 
@@ -357,7 +367,8 @@
                 false /* current */,
                 false /* isAddUser */,
                 false /* isRestricted */,
-                true /* isSwitchToEnabled */)
+                true /* isSwitchToEnabled */,
+                false /* isAddSupervisedUser */)
         `when`(userTracker.userId).thenReturn(guestId)
         `when`(userTracker.userInfo).thenReturn(guestInfo)
 
@@ -389,7 +400,7 @@
             userSwitcherController.users.add(UserSwitcherController.UserRecord(
                     UserInfo(id, name, 0),
                     null, false, isCurrent, false,
-                    false, false
+                    false, false, false
             ))
         }
         val bgUserName = "background_user"
@@ -412,4 +423,42 @@
         `when`(userTracker.userId).thenReturn(1)
         assertEquals(false, userSwitcherController.isSystemUser)
     }
+
+    @Test
+    fun testCanCreateSupervisedUserWithConfiguredPackage() {
+        // GIVEN the supervised user creation package is configured
+        `when`(context.getString(
+            com.android.internal.R.string.config_supervisedUserCreationPackage))
+            .thenReturn("some_pkg")
+
+        // AND the current user is allowed to create new users
+        `when`(userTracker.userId).thenReturn(ownerId)
+        `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+        // WHEN the controller is started with the above config
+        setupController()
+        testableLooper.processAllMessages()
+
+        // THEN a supervised user can be constructed
+        assertTrue(userSwitcherController.canCreateSupervisedUser())
+    }
+
+    @Test
+    fun testCannotCreateSupervisedUserWithConfiguredPackage() {
+        // GIVEN the supervised user creation package is NOT configured
+        `when`(context.getString(
+            com.android.internal.R.string.config_supervisedUserCreationPackage))
+            .thenReturn(null)
+
+        // AND the current user is allowed to create new users
+        `when`(userTracker.userId).thenReturn(ownerId)
+        `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+        // WHEN the controller is started with the above config
+        setupController()
+        testableLooper.processAllMessages()
+
+        // THEN a supervised user can NOT be constructed
+        assertFalse(userSwitcherController.canCreateSupervisedUser())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index d645449..dff77f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.util.condition;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -24,16 +25,21 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
@@ -46,6 +52,7 @@
     private FakeCondition mCondition2;
     private FakeCondition mCondition3;
     private HashSet<Condition> mConditions;
+    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     private Monitor mConditionMonitor;
 
@@ -58,7 +65,83 @@
         mCondition3 = spy(new FakeCondition());
         mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
 
-        mConditionMonitor = new Monitor(mConditions, null /*callbacks*/);
+        mConditionMonitor = new Monitor(mExecutor, mConditions, null /*callbacks*/);
+    }
+
+    @Test
+    public void testOverridingCondition() {
+        final Condition overridingCondition = Mockito.mock(Condition.class);
+        final Condition regularCondition = Mockito.mock(Condition.class);
+        final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
+
+        final Monitor monitor = new Monitor(
+                mExecutor,
+                new HashSet<>(Arrays.asList(overridingCondition, regularCondition)),
+                new HashSet<>(Arrays.asList(callback)));
+
+        when(overridingCondition.isOverridingCondition()).thenReturn(true);
+        when(overridingCondition.isConditionMet()).thenReturn(true);
+        when(regularCondition.isConditionMet()).thenReturn(false);
+
+        final ArgumentCaptor<Condition.Callback> mCallbackCaptor =
+                ArgumentCaptor.forClass(Condition.Callback.class);
+
+        verify(overridingCondition).addCallback(mCallbackCaptor.capture());
+
+        mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
+        mExecutor.runAllReady();
+
+        verify(callback).onConditionsChanged(eq(true));
+        Mockito.clearInvocations(callback);
+
+        when(regularCondition.isConditionMet()).thenReturn(true);
+        when(overridingCondition.isConditionMet()).thenReturn(false);
+
+        mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
+        mExecutor.runAllReady();
+
+        verify(callback).onConditionsChanged(eq(false));
+
+        clearInvocations(callback);
+        monitor.removeCondition(overridingCondition);
+        mExecutor.runAllReady();
+
+        verify(callback).onConditionsChanged(eq(true));
+    }
+
+    /**
+     * Ensures that when multiple overriding conditions are present, it is the aggregate of those
+     * conditions that are considered.
+     */
+    @Test
+    public void testMultipleOverridingConditions() {
+        final Condition overridingCondition = Mockito.mock(Condition.class);
+        final Condition overridingCondition2 = Mockito.mock(Condition.class);
+        final Condition regularCondition = Mockito.mock(Condition.class);
+        final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
+
+        final Monitor monitor = new Monitor(
+                mExecutor,
+                new HashSet<>(Arrays.asList(overridingCondition, overridingCondition2,
+                        regularCondition)),
+                new HashSet<>(Arrays.asList(callback)));
+
+        when(overridingCondition.isOverridingCondition()).thenReturn(true);
+        when(overridingCondition.isConditionMet()).thenReturn(true);
+        when(overridingCondition2.isOverridingCondition()).thenReturn(true);
+        when(overridingCondition.isConditionMet()).thenReturn(false);
+        when(regularCondition.isConditionMet()).thenReturn(true);
+
+        final ArgumentCaptor<Condition.Callback> mCallbackCaptor =
+                ArgumentCaptor.forClass(Condition.Callback.class);
+
+        verify(overridingCondition).addCallback(mCallbackCaptor.capture());
+
+        mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
+        mExecutor.runAllReady();
+
+        verify(callback).onConditionsChanged(eq(false));
+        Mockito.clearInvocations(callback);
     }
 
     @Test
@@ -66,11 +149,13 @@
         final Monitor.Callback callback1 =
                 mock(Monitor.Callback.class);
         mConditionMonitor.addCallback(callback1);
+        mExecutor.runAllReady();
         mConditions.forEach(condition -> verify(condition).addCallback(any()));
 
         final Monitor.Callback callback2 =
                 mock(Monitor.Callback.class);
         mConditionMonitor.addCallback(callback2);
+        mExecutor.runAllReady();
         mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any()));
     }
 
@@ -79,6 +164,7 @@
         final Monitor.Callback callback =
                 mock(Monitor.Callback.class);
         mConditionMonitor.addCallback(callback);
+        mExecutor.runAllReady();
         verify(callback).onConditionsChanged(false);
     }
 
@@ -86,38 +172,53 @@
     public void addCallback_addSecondCallback_reportWithExistingValue() {
         final Monitor.Callback callback1 =
                 mock(Monitor.Callback.class);
-        mConditionMonitor.addCallback(callback1);
-
-        mConditionMonitor.overrideAllConditionsMet(true);
+        final Condition condition = mock(Condition.class);
+        when(condition.isConditionMet()).thenReturn(true);
+        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
+                new HashSet<>(Arrays.asList(callback1)));
 
         final Monitor.Callback callback2 =
                 mock(Monitor.Callback.class);
-        mConditionMonitor.addCallback(callback2);
-        verify(callback2).onConditionsChanged(true);
+        monitor.addCallback(callback2);
+        mExecutor.runAllReady();
+        verify(callback2).onConditionsChanged(eq(true));
     }
 
     @Test
     public void addCallback_noConditions_reportAllConditionsMet() {
-        final Monitor monitor = new Monitor(new HashSet<>(), null /*callbacks*/);
+        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/);
         final Monitor.Callback callback = mock(Monitor.Callback.class);
 
         monitor.addCallback(callback);
-
+        mExecutor.runAllReady();
         verify(callback).onConditionsChanged(true);
     }
 
     @Test
     public void removeCallback_shouldNoLongerReceiveUpdate() {
+        final Condition condition = mock(Condition.class);
+        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
+                null);
         final Monitor.Callback callback =
                 mock(Monitor.Callback.class);
-        mConditionMonitor.addCallback(callback);
+        monitor.addCallback(callback);
+        monitor.removeCallback(callback);
+        mExecutor.runAllReady();
         clearInvocations(callback);
-        mConditionMonitor.removeCallback(callback);
 
-        mConditionMonitor.overrideAllConditionsMet(true);
+        final ArgumentCaptor<Condition.Callback> conditionCallbackCaptor =
+                ArgumentCaptor.forClass(Condition.Callback.class);
+        verify(condition).addCallback(conditionCallbackCaptor.capture());
+        final Condition.Callback conditionCallback = conditionCallbackCaptor.getValue();
+
+        when(condition.isConditionMet()).thenReturn(true);
+        conditionCallback.onConditionChanged(condition);
+        mExecutor.runAllReady();
         verify(callback, never()).onConditionsChanged(true);
 
-        mConditionMonitor.overrideAllConditionsMet(false);
+        when(condition.isConditionMet()).thenReturn(false);
+        conditionCallback.onConditionChanged(condition);
+        mExecutor.runAllReady();
         verify(callback, never()).onConditionsChanged(false);
     }
 
@@ -131,9 +232,11 @@
         mConditionMonitor.addCallback(callback2);
 
         mConditionMonitor.removeCallback(callback1);
+        mExecutor.runAllReady();
         mConditions.forEach(condition -> verify(condition, never()).removeCallback(any()));
 
         mConditionMonitor.removeCallback(callback2);
+        mExecutor.runAllReady();
         mConditions.forEach(condition -> verify(condition).removeCallback(any()));
     }
 
@@ -147,6 +250,7 @@
         mCondition1.fakeUpdateCondition(true);
         mCondition2.fakeUpdateCondition(true);
         mCondition3.fakeUpdateCondition(true);
+        mExecutor.runAllReady();
 
         verify(callback).onConditionsChanged(true);
     }
@@ -163,6 +267,7 @@
         clearInvocations(callback);
 
         mCondition1.fakeUpdateCondition(false);
+        mExecutor.runAllReady();
         verify(callback).onConditionsChanged(false);
     }
 
@@ -171,16 +276,20 @@
         final Monitor.Callback callback =
                 mock(Monitor.Callback.class);
         mConditionMonitor.addCallback(callback);
+        mExecutor.runAllReady();
         verify(callback).onConditionsChanged(false);
         clearInvocations(callback);
 
         mCondition1.fakeUpdateCondition(true);
+        mExecutor.runAllReady();
         verify(callback, never()).onConditionsChanged(anyBoolean());
 
         mCondition2.fakeUpdateCondition(true);
+        mExecutor.runAllReady();
         verify(callback, never()).onConditionsChanged(anyBoolean());
 
         mCondition3.fakeUpdateCondition(true);
+        mExecutor.runAllReady();
         verify(callback).onConditionsChanged(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
index 7fc6b51..9e0f863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.util.condition;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -73,7 +74,8 @@
 
         final Condition.Callback callback2 = mock(Condition.Callback.class);
         mCondition.addCallback(callback2);
-        verify(callback2).onConditionChanged(mCondition, true);
+        verify(callback2).onConditionChanged(mCondition);
+        assertThat(mCondition.isConditionMet()).isTrue();
     }
 
     @Test
@@ -94,7 +96,8 @@
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(true);
-        verify(callback).onConditionChanged(eq(mCondition), eq(true));
+        verify(callback).onConditionChanged(eq(mCondition));
+        assertThat(mCondition.isConditionMet()).isTrue();
     }
 
     @Test
@@ -105,7 +108,8 @@
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(false);
-        verify(callback).onConditionChanged(eq(mCondition), eq(false));
+        verify(callback).onConditionChanged(eq(mCondition));
+        assertThat(mCondition.isConditionMet()).isFalse();
     }
 
     @Test
@@ -116,7 +120,7 @@
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(true);
-        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+        verify(callback, never()).onConditionChanged(eq(mCondition));
     }
 
     @Test
@@ -127,6 +131,6 @@
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(false);
-        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+        verify(callback, never()).onConditionChanged(eq(mCondition));
     }
 }
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
index dafe7a4..daead0a 100644
--- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
+++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
@@ -58,11 +58,14 @@
 
     private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
 
+    private final Context mContext;
+
     public CloudSearchManagerService(Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                         R.string.config_defaultCloudSearchService), null,
                 PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mContext = context;
     }
 
     @Override
@@ -106,6 +109,8 @@
         @Override
         public void search(@NonNull SearchRequest searchRequest,
                 @NonNull ICloudSearchManagerCallback callBack) {
+            searchRequest.setSource(
+                    mContext.getPackageManager().getNameForUid(Binder.getCallingUid()));
             runForUserLocked("search", searchRequest.getRequestId(), (service) ->
                     service.onSearchLocked(searchRequest, callBack));
         }
diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
index 0509e0c..55246e1 100644
--- a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
+++ b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
@@ -16,7 +16,9 @@
 
 package com.android.server.companion;
 
-import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+import static com.android.server.companion.CompanionDeviceManagerService.TAG;
+
+import static java.util.concurrent.TimeUnit.DAYS;
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
@@ -37,17 +39,16 @@
  */
 public class AssociationCleanUpService extends JobService {
     private static final int JOB_ID = AssociationCleanUpService.class.hashCode();
-    private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day
-    private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService(
-            CompanionDeviceManagerServiceInternal.class);
+    private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
 
     @Override
     public boolean onStartJob(final JobParameters params) {
-        Slog.i(LOG_TAG, "Execute the Association CleanUp job");
+        Slog.i(TAG, "Execute the Association CleanUp job");
         // Special policy for APP_STREAMING role that need to revoke associations if the device
         // does not connect for 3 months.
         AsyncTask.execute(() -> {
-            mCdmServiceInternal.associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
+            LocalServices.getService(CompanionDeviceManagerServiceInternal.class)
+                    .associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
             jobFinished(params, false);
         });
         return true;
@@ -55,7 +56,7 @@
 
     @Override
     public boolean onStopJob(final JobParameters params) {
-        Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId()
+        Slog.i(TAG, "Association cleanup job stopped; id=" + params.getJobId()
                 + ", reason="
                 + JobParameters.getInternalReasonCodeDescription(
                 params.getInternalStopReasonCode()));
@@ -63,7 +64,7 @@
     }
 
     static void schedule(Context context) {
-        Slog.i(LOG_TAG, "Scheduling the Association Cleanup job");
+        Slog.i(TAG, "Scheduling the Association Cleanup job");
         final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
         final JobInfo job = new JobInfo.Builder(JOB_ID,
                 new ComponentName(context, AssociationCleanUpService.class))
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index cef0e83..eaa99f7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -18,31 +18,28 @@
 package com.android.server.companion;
 
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
-import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
-import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
-import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Binder.getCallingUid;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.UserHandle.getCallingUserId;
 
 import static com.android.internal.util.CollectionUtils.any;
-import static com.android.internal.util.CollectionUtils.find;
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
 import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
-import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
+import static com.android.server.companion.PackageUtils.getPackageInfo;
 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
 import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
+import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
 import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
 import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
 
 import static java.util.Objects.requireNonNull;
+import static java.util.concurrent.TimeUnit.DAYS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 
 import android.annotation.NonNull;
@@ -53,26 +50,15 @@
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.DeviceNotAssociatedException;
 import android.companion.IAssociationRequestCallback;
 import android.companion.ICompanionDeviceManager;
 import android.companion.IOnAssociationsChangedListener;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.SharedPreferences;
-import android.content.pm.FeatureInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
@@ -93,11 +79,10 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.permission.PermissionControllerManager;
 import android.text.BidiFormatter;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -112,86 +97,49 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
 import com.android.server.pm.UserManagerInternal;
-import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
-import java.util.TimeZone;
 
-/** @hide */
 @SuppressLint("LongLogTag")
-public class CompanionDeviceManagerService extends SystemService
-        implements AssociationStore.OnChangeListener {
-    static final String LOG_TAG = "CompanionDeviceManagerService";
+public class CompanionDeviceManagerService extends SystemService {
+    static final String TAG = "CompanionDeviceManagerService";
     static final boolean DEBUG = false;
 
     /** Range of Association IDs allocated for a user.*/
-    static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
-
-    private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000;
-    private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000;
-
-    static final long DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS = 10 * 1000;
-
+    private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
     private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
 
     private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
     private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
 
-    private static final long ASSOCIATION_CLEAN_UP_TIME_WINDOW =
-            90L * 24 * 60 * 60 * 1000; // 3 months
+    private static final long ASSOCIATION_CLEAN_UP_TIME_WINDOW = DAYS.toMillis(3 * 30); // 3 months
 
-    private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-    static {
-        sDateFormat.setTimeZone(TimeZone.getDefault());
-    }
-
-    // Persistent data store for all Associations.
     private PersistentDataStore mPersistentStore;
-    private final AssociationStoreImpl mAssociationStore = new AssociationStoreImpl();
+    private final PersistUserStateHandler mUserPersistenceHandler;
+
+    private final AssociationStoreImpl mAssociationStore;
     private AssociationRequestsProcessor mAssociationRequestsProcessor;
+    private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+    private CompanionApplicationController mCompanionAppController;
 
-    private PowerWhitelistManager mPowerWhitelistManager;
-    private IAppOpsService mAppOpsManager;
-    private BluetoothAdapter mBluetoothAdapter;
-    private UserManager mUserManager;
-
-    private ScanCallback mBleScanCallback = new BleScanCallback();
-    PermissionControllerManager mPermissionControllerManager;
-
-    private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener =
-            new BluetoothDeviceConnectedListener();
-    private BleStateBroadcastReceiver mBleStateBroadcastReceiver = new BleStateBroadcastReceiver();
-    private List<String> mCurrentlyConnectedDevices = new ArrayList<>();
-    Set<Integer> mPresentSelfManagedDevices = new HashSet<>();
-    private ArrayMap<String, Date> mDevicesLastNearby = new ArrayMap<>();
-    private UnbindDeviceListenersRunnable
-            mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable();
-    private ArrayMap<String, TriggerDeviceDisappearedRunnable> mTriggerDeviceDisappearedRunnables =
-            new ArrayMap<>();
-    private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
-            new RemoteCallbackList<>();
-    private final CompanionDeviceManagerServiceInternal mLocalService = new LocalService(this);
-
-    final Handler mMainHandler = Handler.getMain();
-    private final PersistUserStateHandler mUserPersistenceHandler = new PersistUserStateHandler();
-    private CompanionDevicePresenceController mCompanionDevicePresenceController;
+    private final ActivityManagerInternal mAmInternal;
+    private final IAppOpsService mAppOpsManager;
+    private final PowerWhitelistManager mPowerWhitelistManager;
+    private final UserManager mUserManager;
+    final PackageManagerInternal mPackageManagerInternal;
 
     /**
-     * A structure that consist of two nested maps, and effectively maps (userId + packageName) to
+     * A structure that consists of two nested maps, and effectively maps (userId + packageName) to
      * a list of IDs that have been previously assigned to associations for that package.
      * We maintain this structure so that we never re-use association IDs for the same package
      * (until it's uninstalled).
@@ -199,9 +147,8 @@
     @GuardedBy("mPreviouslyUsedIds")
     private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
 
-    ActivityTaskManagerInternal mAtmInternal;
-    ActivityManagerInternal mAmInternal;
-    PackageManagerInternal mPackageManagerInternal;
+    private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
+            new RemoteCallbackList<>();
 
     public CompanionDeviceManagerService(Context context) {
         super(context);
@@ -209,14 +156,12 @@
         mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
         mAppOpsManager = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
-        mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mPermissionControllerManager = requireNonNull(
-                context.getSystemService(PermissionControllerManager.class));
         mUserManager = context.getSystemService(UserManager.class);
 
-        LocalServices.addService(CompanionDeviceManagerServiceInternal.class, mLocalService);
+        mUserPersistenceHandler = new PersistUserStateHandler();
+        mAssociationStore = new AssociationStoreImpl();
     }
 
     @Override
@@ -224,14 +169,24 @@
         mPersistentStore = new PersistentDataStore();
 
         loadAssociationsFromDisk();
-        mAssociationStore.registerListener(this);
+        mAssociationStore.registerListener(mAssociationStoreChangeListener);
 
-        mCompanionDevicePresenceController = new CompanionDevicePresenceController(this);
-        mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mAssociationStore);
+        mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(
+                mAssociationStore, mDevicePresenceCallback);
 
-        // Publish "binder service"
+        mAssociationRequestsProcessor = new AssociationRequestsProcessor(
+                /* cdmService */this, mAssociationStore);
+
+        final Context context = getContext();
+        mCompanionAppController = new CompanionApplicationController(
+                context, mApplicationControllerCallback);
+
+        // Publish "binder" service.
         final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
         publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl);
+
+        // Publish "local" service.
+        LocalServices.addService(CompanionDeviceManagerServiceInternal.class, new LocalService());
     }
 
     void loadAssociationsFromDisk() {
@@ -248,21 +203,13 @@
 
     @Override
     public void onBootPhase(int phase) {
-        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
-            registerPackageMonitor();
-
-            // Init Bluetooth
-            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-            if (mBluetoothAdapter != null) {
-                mBluetoothAdapter.registerBluetoothConnectionCallback(
-                        getContext().getMainExecutor(),
-                        mBluetoothDeviceConnectedListener);
-                getContext().registerReceiver(
-                        mBleStateBroadcastReceiver, mBleStateBroadcastReceiver.mIntentFilter);
-                initBleScanning();
-            } else {
-                Slog.w(LOG_TAG, "No BluetoothAdapter available");
-            }
+        final Context context = getContext();
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            // WARNING: moving PackageMonitor to another thread (Looper) may introduce significant
+            // delays (even in case of the Main Thread). It may be fine overall, but would require
+            // updating the tests (adding a delay there).
+            mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true);
+            mDevicePresenceMonitor.init(context);
         } else if (phase == PHASE_BOOT_COMPLETED) {
             // Run the Association CleanUp job service daily.
             AssociationCleanUpService.schedule(getContext());
@@ -288,76 +235,84 @@
             @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
         final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
                 userId, packageName, macAddress);
-        return sanitizeWithCallerChecks(association);
+        return sanitizeWithCallerChecks(getContext(), association);
     }
 
     @Nullable
     AssociationInfo getAssociationWithCallerChecks(int associationId) {
         final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
-        return sanitizeWithCallerChecks(association);
+        return sanitizeWithCallerChecks(getContext(), association);
     }
 
-    @Nullable
-    private AssociationInfo sanitizeWithCallerChecks(@Nullable AssociationInfo association) {
-        if (association == null) return null;
+    private void onDeviceAppearedInternal(int associationId) {
+        if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId);
+
+        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+        if (DEBUG) Log.d(TAG, "  association=" + associationId);
+
+        if (!association.shouldBindWhenPresent()) return;
 
         final int userId = association.getUserId();
         final String packageName = association.getPackageName();
-        if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
-            return null;
-        }
 
-        return association;
+        if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+            mCompanionAppController.bindCompanionApplication(userId, packageName);
+        } else if (DEBUG) {
+            Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
+        }
+        mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association);
     }
 
-    // Revoke associations if the selfManaged companion device does not connect for 3
-    // months for specific profile.
-    private void associationCleanUp(String profile) {
-        for (AssociationInfo ai : mAssociationStore.getAssociations()) {
-            if (ai.isSelfManaged()
-                    && profile.equals(ai.getDeviceProfile())
-                    && System.currentTimeMillis() - ai.getLastTimeConnectedMs()
-                    >= ASSOCIATION_CLEAN_UP_TIME_WINDOW) {
-                Slog.d(LOG_TAG, "Removing the association for associationId: "
-                        + ai.getId()
-                        + " due to the device does not connect for 3 months."
-                        + " Current time: "
-                        + new Date(System.currentTimeMillis()));
-                disassociateInternal(ai.getId());
-            }
+    private void onDeviceDisappearedInternal(int associationId) {
+        if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId);
+
+        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+        if (DEBUG) Log.d(TAG, "  association=" + associationId);
+
+        final int userId = association.getUserId();
+        final String packageName = association.getPackageName();
+
+        if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+            if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
+            return;
         }
+
+        if (association.shouldBindWhenPresent()) {
+            mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association);
+        }
+
+        // Check if there are other devices associated to the app that are present.
+        if (shouldBindPackage(userId, packageName)) return;
+
+        mCompanionAppController.unbindCompanionApplication(userId, packageName);
     }
 
-    void maybeGrantAutoRevokeExemptions() {
-        Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()");
-        PackageManager pm = getContext().getPackageManager();
-        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
-            SharedPreferences pref = getContext().getSharedPreferences(
-                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
-                    Context.MODE_PRIVATE);
-            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
-                continue;
-            }
-
-            try {
-                final List<AssociationInfo> associations =
-                        mAssociationStore.getAssociationsForUser(userId);
-                for (AssociationInfo a : associations) {
-                    try {
-                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
-                        exemptFromAutoRevoke(a.getPackageName(), uid);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        Slog.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e);
-                    }
-                }
-            } finally {
-                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
-            }
-        }
+    private boolean onCompanionApplicationBindingDiedInternal(
+            @UserIdInt int userId, @NonNull String packageName) {
+        // TODO(b/218613015): implement.
+        return false;
     }
 
-    @Override
-    public void onAssociationChanged(
+    private void onRebindCompanionApplicationTimeoutInternal(
+            @UserIdInt int userId, @NonNull String packageName) {
+        // TODO(b/218613015): implement.
+    }
+
+    /**
+     * @return whether the package should be bound (i.e. at least one of the devices associated with
+     *         the package is currently present).
+     */
+    private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
+        final List<AssociationInfo> packageAssociations =
+                mAssociationStore.getAssociationsForPackage(userId, packageName);
+        for (AssociationInfo association : packageAssociations) {
+            if (!association.shouldBindWhenPresent()) continue;
+            if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true;
+        }
+        return false;
+    }
+
+    private void onAssociationChangedInternal(
             @AssociationStore.ChangeType int changeType, AssociationInfo association) {
         final int id = association.getId();
         final int userId = association.getUserId();
@@ -379,8 +334,6 @@
             notifyListeners(userId, updatedAssociations);
         }
         updateAtm(userId, updatedAssociations);
-
-        restartBleScan();
     }
 
     private void persistStateForUser(@UserIdInt int userId) {
@@ -417,15 +370,59 @@
         }
     }
 
-    class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+    private void onPackageRemoveOrDataClearedInternal(
+            @UserIdInt int userId, @NonNull String packageName) {
+        if (DEBUG) {
+            Log.i(TAG, "onPackageRemove_Or_DataCleared() u" + userId + "/"
+                    + packageName);
+        }
 
+        // Clear associations.
+        final List<AssociationInfo> associationsForPackage =
+                mAssociationStore.getAssociationsForPackage(userId, packageName);
+        for (AssociationInfo association : associationsForPackage) {
+            mAssociationStore.removeAssociation(association.getId());
+        }
+
+        mCompanionAppController.onPackagesChanged(userId);
+    }
+
+    private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
+        if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName);
+
+        final List<AssociationInfo> associationsForPackage =
+                mAssociationStore.getAssociationsForPackage(userId, packageName);
+        for (AssociationInfo association : associationsForPackage) {
+            updateSpecialAccessPermissionForAssociatedPackage(association);
+        }
+
+        mCompanionAppController.onPackagesChanged(userId);
+    }
+
+    // Revoke associations if the selfManaged companion device does not connect for 3
+    // months for specific profile.
+    private void associationCleanUp(String profile) {
+        for (AssociationInfo ai : mAssociationStore.getAssociations()) {
+            if (ai.isSelfManaged()
+                    && profile.equals(ai.getDeviceProfile())
+                    && System.currentTimeMillis() - ai.getLastTimeConnectedMs()
+                    >= ASSOCIATION_CLEAN_UP_TIME_WINDOW) {
+                Slog.i(TAG, "Removing the association for associationId: "
+                        + ai.getId()
+                        + " due to the device does not connect for 3 months.");
+                disassociateInternal(ai.getId());
+            }
+        }
+    }
+
+    class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
             try {
                 return super.onTransact(code, data, reply, flags);
             } catch (Throwable e) {
-                Slog.e(LOG_TAG, "Error during IPC", e);
+                Slog.e(TAG, "Error during IPC", e);
                 throw ExceptionUtils.propagate(e, RemoteException.class);
             }
         }
@@ -433,7 +430,7 @@
         @Override
         public void associate(AssociationRequest request, IAssociationRequestCallback callback,
                 String packageName, int userId) throws RemoteException {
-            Slog.i(LOG_TAG, "associate() "
+            Slog.i(TAG, "associate() "
                     + "request=" + request + ", "
                     + "package=u" + userId + "/" + packageName);
             enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
@@ -451,7 +448,7 @@
             if (!checkCallerCanManageCompanionDevice(getContext())) {
                 // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to
                 // request the feature (also: the caller is the app itself).
-                checkUsesFeature(packageName, getCallingUserId());
+                enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
             }
 
             return mAssociationStore.getAssociationsForPackage(userId, packageName);
@@ -487,6 +484,11 @@
 
         @Override
         public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
+            if (DEBUG) {
+                Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName
+                        + ", macAddress=" + deviceMacAddress);
+            }
+
             requireNonNull(deviceMacAddress);
             requireNonNull(packageName);
 
@@ -503,6 +505,8 @@
 
         @Override
         public void disassociate(int associationId) {
+            if (DEBUG) Log.i(TAG, "disassociate() associationId=" + associationId);
+
             final AssociationInfo association = getAssociationWithCallerChecks(associationId);
             if (association == null) {
                 throw new IllegalArgumentException("Association with ID " + associationId + " "
@@ -519,9 +523,9 @@
                 throws RemoteException {
             String callingPackage = component.getPackageName();
             checkCanCallNotificationApi(callingPackage);
-            //TODO: check userId.
+            // TODO: check userId.
             String packageTitle = BidiFormatter.getInstance().unicodeWrap(
-                    getPackageInfo(callingPackage, userId)
+                    getPackageInfo(getContext(), userId, callingPackage)
                             .applicationInfo
                             .loadSafeLabel(getContext().getPackageManager(),
                                     PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
@@ -575,26 +579,28 @@
         @Override
         public void registerDevicePresenceListenerService(String deviceAddress,
                 String callingPackage, int userId) throws RemoteException {
-            //TODO: take the userId into account.
+            // TODO: take the userId into account.
             registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
         }
 
         @Override
         public void unregisterDevicePresenceListenerService(String deviceAddress,
                 String callingPackage, int userId) throws RemoteException {
-            //TODO: take the userId into account.
+            // TODO: take the userId into account.
             registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
         }
 
         @Override
         public void dispatchMessage(int messageId, int associationId, byte[] message)
                 throws RemoteException {
-            //TODO: b/199427116
+            // TODO(b/199427116): implement.
         }
 
         @Override
         public void notifyDeviceAppeared(int associationId) {
-            final AssociationInfo association = getAssociationWithCallerChecks(associationId);
+            if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
+
+            AssociationInfo association = getAssociationWithCallerChecks(associationId);
             if (association == null) {
                 throw new IllegalArgumentException("Association with ID " + associationId + " "
                         + "does not exist "
@@ -607,23 +613,20 @@
                         + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
                         + " self-managed associations.");
             }
-
-            if (!mPresentSelfManagedDevices.add(associationId)) {
-                Slog.w(LOG_TAG, "Association with ID " + associationId + " is already present");
-                return;
-            }
-
-            AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association)
+            // AssociationInfo class is immutable: create a new AssociationInfo object with updated
+            // timestamp.
+            association = AssociationInfo.builder(association)
                     .setLastTimeConnected(System.currentTimeMillis())
                     .build();
-            mAssociationStore.updateAssociation(updatedAssociationInfo);
+            mAssociationStore.updateAssociation(association);
 
-            mCompanionDevicePresenceController.onDeviceNotifyAppeared(
-                    updatedAssociationInfo, getContext(), mMainHandler);
+            mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
         }
 
         @Override
         public void notifyDeviceDisappeared(int associationId) {
+            if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId);
+
             final AssociationInfo association = getAssociationWithCallerChecks(associationId);
             if (association == null) {
                 throw new IllegalArgumentException("Association with ID " + associationId + " "
@@ -638,14 +641,7 @@
                         + " self-managed associations.");
             }
 
-            if (!mPresentSelfManagedDevices.contains(associationId)) {
-                Slog.w(LOG_TAG, "Association with ID " + associationId + " is not connected");
-                return;
-            }
-
-            mPresentSelfManagedDevices.remove(associationId);
-            mCompanionDevicePresenceController.onDeviceNotifyDisappearedAndUnbind(
-                    association, getContext(), mMainHandler);
+            mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
         }
 
         private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
@@ -656,8 +652,7 @@
             final int userId = getCallingUserId();
             enforceCallerIsSystemOr(userId, packageName);
 
-            final AssociationInfo association =
-                    mAssociationStore.getAssociationsForPackageWithAddress(
+            AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
                             userId, packageName, deviceAddress);
 
             if (association == null) {
@@ -666,10 +661,14 @@
                         + " for user " + userId));
             }
 
-            AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association)
+            // AssociationInfo class is immutable: create a new AssociationInfo object with updated
+            // flag.
+            association = AssociationInfo.builder(association)
                     .setNotifyOnDeviceNearby(active)
                     .build();
-            mAssociationStore.updateAssociation(updatedAssociationInfo);
+            mAssociationStore.updateAssociation(association);
+
+            // TODO(b/218615198): correctly handle the case when the device is currently present.
         }
 
         @Override
@@ -677,7 +676,7 @@
                 byte[] certificate) {
             if (!getContext().getPackageManager().hasSigningCertificate(
                     packageName, certificate, CERT_INPUT_SHA256)) {
-                Slog.e(LOG_TAG, "Given certificate doesn't match the package certificate.");
+                Slog.e(TAG, "Given certificate doesn't match the package certificate.");
                 return;
             }
 
@@ -691,10 +690,12 @@
             final int userId = getCallingUserId();
             enforceCallerIsSystemOr(userId, callingPackage);
 
+            if (getCallingUid() == SYSTEM_UID) return;
+
+            enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage);
             checkState(!ArrayUtils.isEmpty(
                     mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
                     "App must have an association before calling this API");
-            checkUsesFeature(callingPackage, userId);
         }
 
         @Override
@@ -720,47 +721,20 @@
         }
 
         @Override
-        public void dump(@NonNull FileDescriptor fd,
-                @NonNull PrintWriter fout,
+        public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter out,
                 @Nullable String[] args) {
-            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) {
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, out)) {
                 return;
             }
 
-            fout.append("Companion Device Associations:").append('\n');
+            // TODO(b/218615185): mAssociationStore.dump() instead
+            out.append("Companion Device Associations:").append('\n');
             for (AssociationInfo a : mAssociationStore.getAssociations()) {
-                fout.append("  ").append(a.toString()).append('\n');
+                out.append("  ").append(a.toString()).append('\n');
             }
 
-            fout.append("Currently Connected Devices:").append('\n');
-            for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) {
-                fout.append("  ").append(mCurrentlyConnectedDevices.get(i)).append('\n');
-            }
-
-            fout.append("Currently SelfManaged Connected Devices associationId:").append('\n');
-            for (Integer associationId : mPresentSelfManagedDevices) {
-                fout.append("  ").append("AssociationId:  ").append(
-                        String.valueOf(associationId)).append('\n');
-            }
-
-            fout.append("Devices Last Nearby:").append('\n');
-            for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) {
-                String device = mDevicesLastNearby.keyAt(i);
-                Date time = mDevicesLastNearby.valueAt(i);
-                fout.append("  ").append(device).append(" -> ")
-                        .append(sDateFormat.format(time)).append('\n');
-            }
-
-            fout.append("Device Listener Services State:").append('\n');
-            for (int i = 0, size =  mCompanionDevicePresenceController.mBoundServices.size();
-                    i < size; i++) {
-                int userId = mCompanionDevicePresenceController.mBoundServices.keyAt(i);
-                fout.append("  ")
-                        .append("u").append(Integer.toString(userId)).append(": ")
-                        .append(Objects.toString(
-                                mCompanionDevicePresenceController.mBoundServices.valueAt(i)))
-                        .append('\n');
-            }
+            // TODO(b/218615185): mDevicePresenceMonitor.dump()
+            // TODO(b/218615185): mCompanionAppController.dump()
         }
     }
 
@@ -784,7 +758,7 @@
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
                 macAddress, displayName, deviceProfile, selfManaged, false, timestamp,
                 Long.MAX_VALUE);
-        Slog.i(LOG_TAG, "New CDM association created=" + association);
+        Slog.i(TAG, "New CDM association created=" + association);
         mAssociationStore.addAssociation(association);
 
         // If the "Device Profile" is specified, make the companion application a holder of the
@@ -862,52 +836,50 @@
         }
     }
 
-    //TODO: also revoke notification access
+    // TODO: also revoke notification access
     void disassociateInternal(int associationId) {
-        onAssociationPreRemove(associationId);
-        mAssociationStore.removeAssociation(associationId);
-    }
-
-    void onAssociationPreRemove(int associationId) {
         final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
-        if (association.isNotifyOnDeviceNearby()
-                || (association.isSelfManaged()
-                && mPresentSelfManagedDevices.contains(association.getId()))) {
-            mCompanionDevicePresenceController.unbindDevicePresenceListener(
-                    association.getPackageName(), association.getUserId());
-        }
+        final int userId = association.getUserId();
+        final String packageName = association.getPackageName();
+        final String deviceProfile = association.getDeviceProfile();
 
-        String deviceProfile = association.getDeviceProfile();
+        final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
+
+        // Removing the association.
+        mAssociationStore.removeAssociation(associationId);
+
+        final List<AssociationInfo> otherAssociations =
+                mAssociationStore.getAssociationsForPackage(userId, packageName);
+
+        // Check if the package is associated with other devices with the same profile.
+        // If not: take away the role.
         if (deviceProfile != null) {
-            AssociationInfo otherAssociationWithDeviceProfile = find(
-                    mAssociationStore.getAssociationsForUser(association.getUserId()),
-                    a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile()));
-            if (otherAssociationWithDeviceProfile != null) {
-                Slog.i(LOG_TAG, "Not revoking " + deviceProfile
-                        + " for " + association
-                        + " - profile still present in " + otherAssociationWithDeviceProfile);
-            } else {
-                Binder.withCleanCallingIdentity(
-                        () -> removeRoleHolderForAssociation(getContext(), association));
+            final boolean shouldKeepTheRole = any(otherAssociations,
+                    it -> deviceProfile.equals(it.getDeviceProfile()));
+            if (!shouldKeepTheRole) {
+                Binder.withCleanCallingIdentity(() ->
+                        removeRoleHolderForAssociation(getContext(), association));
             }
         }
+
+        if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
+        // The device was connected and the app was notified: check if we need to unbind the app
+        // now.
+        final boolean shouldStayBound = any(otherAssociations,
+                it -> it.isNotifyOnDeviceNearby()
+                        && mDevicePresenceMonitor.isDevicePresent(it.getId()));
+        if (shouldStayBound) return;
+        mCompanionAppController.unbindCompanionApplication(userId, packageName);
     }
 
     private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
-        PackageInfo packageInfo = getPackageInfo(
-                association.getPackageName(),
-                association.getUserId());
-        if (packageInfo == null) {
-            return;
-        }
+        final PackageInfo packageInfo =
+                getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
 
-        Binder.withCleanCallingIdentity(obtainRunnable(CompanionDeviceManagerService::
-                updateSpecialAccessPermissionAsSystem, this, association, packageInfo)
-                .recycleOnUse());
+        Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
     }
 
-    private void updateSpecialAccessPermissionAsSystem(
-            AssociationInfo association, PackageInfo packageInfo) {
+    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
         if (containsEither(packageInfo.requestedPermissions,
                 android.Manifest.permission.RUN_IN_BACKGROUND,
                 android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
@@ -916,7 +888,7 @@
             try {
                 mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName);
             } catch (UnsupportedOperationException e) {
-                Slog.w(LOG_TAG, packageInfo.packageName + " can't be removed from power save"
+                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
                         + " whitelist. It might due to the package is whitelisted by the system.");
             }
         }
@@ -935,10 +907,6 @@
         }
 
         exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
-
-        if (association.isNotifyOnDeviceNearby()) {
-            restartBleScan();
-        }
     }
 
     private void exemptFromAutoRevoke(String packageName, int uid) {
@@ -949,23 +917,10 @@
                     packageName,
                     AppOpsManager.MODE_IGNORED);
         } catch (RemoteException e) {
-            Slog.w(LOG_TAG,
-                    "Error while granting auto revoke exemption for " + packageName, e);
+            Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e);
         }
     }
 
-    private static <T> boolean containsEither(T[] array, T a, T b) {
-        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
-    }
-
-    @Nullable
-    private PackageInfo getPackageInfo(String packageName, int userId) {
-        final int flags = PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS;
-        return Binder.withCleanCallingIdentity(
-                () -> getContext().getPackageManager()
-                        .getPackageInfoAsUser(packageName, flags , userId));
-    }
-
     private void updateAtm(int userId, List<AssociationInfo> associations) {
         final Set<Integer> companionAppUids = new ArraySet<>();
         for (AssociationInfo association : associations) {
@@ -981,263 +936,86 @@
         }
     }
 
-    void onDeviceConnected(String address) {
-        Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
-        mCurrentlyConnectedDevices.add(address);
-        onDeviceNearby(address);
-    }
+    private void maybeGrantAutoRevokeExemptions() {
+        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
 
-    void onDeviceDisconnected(String address) {
-        Slog.d(LOG_TAG, "onDeviceDisconnected(address = " + address + ")");
-
-        mCurrentlyConnectedDevices.remove(address);
-
-        Date lastSeen = mDevicesLastNearby.get(address);
-        if (isDeviceDisappeared(lastSeen)) {
-            onDeviceDisappeared(address);
-            unscheduleTriggerDeviceDisappearedRunnable(address);
-        }
-    }
-
-    private boolean isDeviceDisappeared(Date lastSeen) {
-        return lastSeen == null || System.currentTimeMillis() - lastSeen.getTime()
-                >= DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS;
-    }
-
-    private class BleScanCallback extends ScanCallback {
-        @Override
-        public void onScanResult(int callbackType, ScanResult result) {
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "onScanResult(callbackType = "
-                        + callbackType + ", result = " + result + ")");
-            }
-
-            onDeviceNearby(result.getDevice().getAddress());
-        }
-
-        @Override
-        public void onBatchScanResults(List<ScanResult> results) {
-            for (int i = 0, size = results.size(); i < size; i++) {
-                onScanResult(CALLBACK_TYPE_ALL_MATCHES, results.get(i));
-            }
-        }
-
-        @Override
-        public void onScanFailed(int errorCode) {
-            if (errorCode == SCAN_FAILED_ALREADY_STARTED) {
-                // ignore - this might happen if BT tries to auto-restore scans for us in the
-                // future
-                Slog.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED");
-            } else {
-                Slog.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode);
-            }
-        }
-    }
-
-    private class BleStateBroadcastReceiver extends BroadcastReceiver {
-
-        final IntentFilter mIntentFilter =
-                new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
-            int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
-            Slog.d(LOG_TAG, "Received BT state transition broadcast: "
-                    + BluetoothAdapter.nameForState(previousState)
-                    + " -> " + BluetoothAdapter.nameForState(newState));
-
-            boolean bleOn = newState == BluetoothAdapter.STATE_ON
-                    || newState == BluetoothAdapter.STATE_BLE_ON;
-            if (bleOn) {
-                if (mBluetoothAdapter.getBluetoothLeScanner() != null) {
-                    startBleScan();
-                } else {
-                    Slog.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null");
-                }
-            }
-        }
-    }
-
-    private class UnbindDeviceListenersRunnable implements Runnable {
-
-        public String getJobId(String address) {
-            return "CDM_deviceGone_unbind_" + address;
-        }
-
-        @Override
-        public void run() {
-            int size = mDevicesLastNearby.size();
-            for (int i = 0; i < size; i++) {
-                String address = mDevicesLastNearby.keyAt(i);
-                Date lastNearby = mDevicesLastNearby.valueAt(i);
-
-                if (isDeviceDisappeared(lastNearby)) {
-                    final List<AssociationInfo> associations =
-                            mAssociationStore.getAssociationsByAddress(address);
-                    for (AssociationInfo association : associations) {
-                        if (association.isNotifyOnDeviceNearby()) {
-                            mCompanionDevicePresenceController.unbindDevicePresenceListener(
-                                    association.getPackageName(), association.getUserId());
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private class TriggerDeviceDisappearedRunnable implements Runnable {
-
-        private final String mAddress;
-
-        TriggerDeviceDisappearedRunnable(String address) {
-            mAddress = address;
-        }
-
-        public void schedule() {
-            mMainHandler.removeCallbacks(this);
-            mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS);
-        }
-
-        @Override
-        public void run() {
-            Slog.d(LOG_TAG, "TriggerDeviceDisappearedRunnable.run(address = " + mAddress + ")");
-            if (!mCurrentlyConnectedDevices.contains(mAddress)) {
-                onDeviceDisappeared(mAddress);
-            }
-        }
-    }
-
-    private void unscheduleTriggerDeviceDisappearedRunnable(String address) {
-        Runnable r = mTriggerDeviceDisappearedRunnables.get(address);
-        if (r != null) {
-            Slog.d(LOG_TAG,
-                    "unscheduling TriggerDeviceDisappearedRunnable(address = " + address + ")");
-            mMainHandler.removeCallbacks(r);
-        }
-    }
-
-    private void onDeviceNearby(String address) {
-        Date timestamp = new Date();
-        Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
-
-        cancelUnbindDeviceListener(address);
-
-        mTriggerDeviceDisappearedRunnables
-                .computeIfAbsent(address, addr -> new TriggerDeviceDisappearedRunnable(address))
-                .schedule();
-
-        // Avoid spamming the app if device is already known to be nearby
-        boolean justAppeared = oldTimestamp == null
-                || timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS;
-        if (justAppeared) {
-            Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
-            final List<AssociationInfo> associations =
-                    mAssociationStore.getAssociationsByAddress(address);
-            for (AssociationInfo association : associations) {
-                if (association.isNotifyOnDeviceNearby()) {
-                    mCompanionDevicePresenceController.onDeviceNotifyAppeared(association,
-                            getContext(), mMainHandler);
-                }
-            }
-        }
-    }
-
-    private void onDeviceDisappeared(String address) {
-        Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")");
-
-        boolean hasDeviceListeners = false;
-        final List<AssociationInfo> associations =
-                mAssociationStore.getAssociationsByAddress(address);
-        for (AssociationInfo association : associations) {
-            if (association.isNotifyOnDeviceNearby()) {
-                mCompanionDevicePresenceController.onDeviceNotifyDisappeared(
-                        association, getContext(), mMainHandler);
-                hasDeviceListeners = true;
-            }
-        }
-
-        cancelUnbindDeviceListener(address);
-        if (hasDeviceListeners) {
-            mMainHandler.postDelayed(
-                    mUnbindDeviceListenersRunnable,
-                    mUnbindDeviceListenersRunnable.getJobId(address),
-                    DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS);
-        }
-    }
-
-    private void cancelUnbindDeviceListener(String address) {
-        mMainHandler.removeCallbacks(
-                mUnbindDeviceListenersRunnable, mUnbindDeviceListenersRunnable.getJobId(address));
-    }
-
-    private void initBleScanning() {
-        Slog.i(LOG_TAG, "initBleScanning()");
-
-        boolean bluetoothReady = mBluetoothAdapter.registerServiceLifecycleCallback(
-                new BluetoothAdapter.ServiceLifecycleCallback() {
-                    @Override
-                    public void onBluetoothServiceUp() {
-                        Slog.i(LOG_TAG, "Bluetooth stack is up");
-                        startBleScan();
-                    }
-
-                    @Override
-                    public void onBluetoothServiceDown() {
-                        Slog.w(LOG_TAG, "Bluetooth stack is down");
-                    }
-                });
-        if (bluetoothReady) {
-            startBleScan();
-        }
-    }
-
-    void startBleScan() {
-        Slog.i(LOG_TAG, "startBleScan()");
-
-        List<ScanFilter> filters = getBleScanFilters();
-        if (filters.isEmpty()) {
-            return;
-        }
-        BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
-        if (scanner == null) {
-            Slog.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)");
-        } else {
-            scanner.startScan(
-                    filters,
-                    new ScanSettings.Builder().setScanMode(SCAN_MODE_LOW_POWER).build(),
-                    mBleScanCallback);
-        }
-    }
-
-    void restartBleScan() {
-        if (mBluetoothAdapter.getBluetoothLeScanner() != null) {
-            mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback);
-            startBleScan();
-        } else {
-            Slog.w(LOG_TAG, "BluetoothLeScanner is null (likely BLE isn't ON yet).");
-        }
-    }
-
-    private List<ScanFilter> getBleScanFilters() {
-        ArrayList<ScanFilter> result = new ArrayList<>();
-        ArraySet<String> addressesSeen = new ArraySet<>();
-        for (AssociationInfo association : mAssociationStore.getAssociations()) {
-            if (association.isSelfManaged()) {
+        PackageManager pm = getContext().getPackageManager();
+        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
+            SharedPreferences pref = getContext().getSharedPreferences(
+                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
+                    Context.MODE_PRIVATE);
+            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
                 continue;
             }
-            String address = association.getDeviceMacAddressAsString();
-            if (addressesSeen.contains(address)) {
-                continue;
-            }
-            if (association.isNotifyOnDeviceNearby()) {
-                result.add(new ScanFilter.Builder().setDeviceAddress(address).build());
-                addressesSeen.add(address);
+
+            try {
+                final List<AssociationInfo> associations =
+                        mAssociationStore.getAssociationsForUser(userId);
+                for (AssociationInfo a : associations) {
+                    try {
+                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
+                        exemptFromAutoRevoke(a.getPackageName(), uid);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
+                    }
+                }
+            } finally {
+                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
             }
         }
-        return result;
     }
 
+    private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
+            new AssociationStore.OnChangeListener() {
+        @Override
+        public void onAssociationChanged(int changeType, AssociationInfo association) {
+            onAssociationChangedInternal(changeType, association);
+        }
+    };
+
+    private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
+            new CompanionDevicePresenceMonitor.Callback() {
+        @Override
+        public void onDeviceAppeared(int associationId) {
+            onDeviceAppearedInternal(associationId);
+        }
+
+        @Override
+        public void onDeviceDisappeared(int associationId) {
+            onDeviceDisappearedInternal(associationId);
+        }
+    };
+
+    private final CompanionApplicationController.Callback mApplicationControllerCallback =
+            new CompanionApplicationController.Callback() {
+        @Override
+        public boolean onCompanionApplicationBindingDied(int userId, @NonNull String packageName) {
+            return onCompanionApplicationBindingDiedInternal(userId, packageName);
+        }
+
+        @Override
+        public void onRebindCompanionApplicationTimeout(int userId, @NonNull String packageName) {
+            onRebindCompanionApplicationTimeoutInternal(userId, packageName);
+        }
+    };
+
+    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName);
+        }
+
+        @Override
+        public void onPackageDataCleared(String packageName, int uid) {
+            onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName);
+        }
+
+        @Override
+        public void onPackageModified(String packageName) {
+            onPackageModifiedInternal(getChangingUserId(), packageName);
+        }
+    };
+
     static int getFirstAssociationIdForUser(@UserIdInt int userId) {
         // We want the IDs to start from 1, not 0.
         return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
@@ -1247,82 +1025,6 @@
         return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
     }
 
-    private class BluetoothDeviceConnectedListener
-            extends BluetoothAdapter.BluetoothConnectionCallback {
-        @Override
-        public void onDeviceConnected(BluetoothDevice device) {
-            CompanionDeviceManagerService.this.onDeviceConnected(device.getAddress());
-        }
-
-        @Override
-        public void onDeviceDisconnected(BluetoothDevice device, int reason) {
-            Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") "
-                    + BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason));
-            CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
-        }
-    }
-
-    void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) {
-        if (getCallingUid() == SYSTEM_UID) return;
-
-        final FeatureInfo[] requestedFeatures = getPackageInfo(pkg, userId).reqFeatures;
-        if (requestedFeatures != null) {
-            for (int i = 0; i < requestedFeatures.length; i++) {
-                if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeatures[i].name)) return;
-            }
-        }
-
-        throw new IllegalStateException("Must declare uses-feature "
-                + FEATURE_COMPANION_DEVICE_SETUP
-                + " in manifest to use this API");
-    }
-
-    private void registerPackageMonitor() {
-        new PackageMonitor() {
-            @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                final int userId = getChangingUserId();
-                Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName);
-
-                clearAssociationForPackage(userId, packageName);
-            }
-
-            @Override
-            public void onPackageDataCleared(String packageName, int uid) {
-                final int userId = getChangingUserId();
-                Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName);
-
-                clearAssociationForPackage(userId, packageName);
-            }
-
-            @Override
-            public void onPackageModified(String packageName) {
-                final int userId = getChangingUserId();
-                Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName);
-
-                final List<AssociationInfo> associationsForPackage =
-                        mAssociationStore.getAssociationsForPackage(userId, packageName);
-                for (AssociationInfo association : associationsForPackage) {
-                    updateSpecialAccessPermissionForAssociatedPackage(association);
-                }
-            }
-        }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
-    }
-
-    private void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) {
-        if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName);
-
-        // First, unbind CompanionService if needed.
-        mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId);
-
-        // Clear associations.
-        final List<AssociationInfo> associationsForPackage =
-                mAssociationStore.getAssociationsForPackage(userId, packageName);
-        for (AssociationInfo association : associationsForPackage) {
-            mAssociationStore.removeAssociation(association.getId());
-        }
-    }
-
     private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
         final Map<String, Set<Integer>> copy = new HashMap<>();
 
@@ -1334,16 +1036,14 @@
         return Collections.unmodifiableMap(copy);
     }
 
-    private final class LocalService extends CompanionDeviceManagerServiceInternal {
-        private final CompanionDeviceManagerService mService;
+    private static <T> boolean containsEither(T[] array, T a, T b) {
+        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
+    }
 
-        LocalService(CompanionDeviceManagerService service) {
-            mService = service;
-        }
-
+    private class LocalService extends CompanionDeviceManagerServiceInternal {
         @Override
         public void associationCleanUp(String profile) {
-            mService.associationCleanUp(profile);
+            CompanionDeviceManagerService.this.associationCleanUp(profile);
         }
     }
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
deleted file mode 100644
index fc66817..0000000
--- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
+++ /dev/null
@@ -1,238 +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.companion;
-
-import static android.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE;
-import static android.content.Context.BIND_IMPORTANT;
-
-import static com.android.internal.util.CollectionUtils.filter;
-
-import android.annotation.NonNull;
-import android.companion.AssociationInfo;
-import android.companion.CompanionDeviceService;
-import android.companion.ICompanionDeviceService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.util.ArrayMap;
-import android.util.Slog;
-
-import com.android.internal.infra.PerUser;
-import com.android.internal.infra.ServiceConnector;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * This class creates/removes {@link ServiceConnector}s between {@link CompanionDeviceService} and
- * the companion apps. The controller also will notify the companion apps with device status.
- */
-public class CompanionDevicePresenceController {
-    private static final String LOG_TAG = "CompanionDevicePresenceController";
-    PerUser<ArrayMap<String, List<BoundService>>> mBoundServices;
-    private static final String META_DATA_KEY_PRIMARY = "android.companion.primary";
-    private final CompanionDeviceManagerService mService;
-
-    public CompanionDevicePresenceController(CompanionDeviceManagerService service) {
-        mService = service;
-        mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() {
-            @NonNull
-            @Override
-            protected ArrayMap<String, List<BoundService>> create(int userId) {
-                return new ArrayMap<>();
-            }
-        };
-    }
-
-    void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) {
-        for (BoundService boundService : getDeviceListenerServiceConnector(
-                association, context,  handler)) {
-            if (boundService.mIsPrimary) {
-                Slog.i(LOG_TAG,
-                        "Sending onDeviceAppeared to " + association.getPackageName() + ")");
-                boundService.mServiceConnector.run(
-                        service -> service.onDeviceAppeared(association));
-            } else {
-                Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName);
-                boundService.mServiceConnector.connect();
-            }
-        }
-    }
-
-    void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) {
-        for (BoundService boundService : getDeviceListenerServiceConnector(
-                association, context,  handler)) {
-            if (boundService.mIsPrimary) {
-                Slog.i(LOG_TAG,
-                        "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
-                boundService.mServiceConnector.run(service ->
-                        service.onDeviceDisappeared(association));
-            }
-        }
-    }
-
-    void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association,
-            Context context, Handler handler) {
-        for (BoundService boundService : getDeviceListenerServiceConnector(
-                association, context, handler)) {
-            if (boundService.mIsPrimary) {
-                Slog.i(LOG_TAG,
-                        "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
-                boundService.mServiceConnector.post(
-                        service -> {
-                            service.onDeviceDisappeared(association);
-                        }).thenRun(() -> unbindDevicePresenceListener(
-                                        association.getPackageName(), association.getUserId()));
-            }
-        }
-    }
-
-    void unbindDevicePresenceListener(String packageName, int userId) {
-        List<BoundService> boundServices = mBoundServices.forUser(userId)
-                .remove(packageName);
-        if (boundServices != null) {
-            for (BoundService boundService: boundServices) {
-                Slog.d(LOG_TAG, "Unbinding the serviceConnector: " + boundService.mComponentName);
-                boundService.mServiceConnector.unbind();
-            }
-        }
-    }
-
-    private List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context,
-            Handler handler) {
-        return mBoundServices.forUser(a.getUserId()).computeIfAbsent(
-                a.getPackageName(),
-                pkg -> createDeviceListenerServiceConnector(a, context, handler));
-    }
-
-    private List<BoundService> createDeviceListenerServiceConnector(AssociationInfo a,
-            Context context, Handler handler) {
-        List<ResolveInfo> resolveInfos = context
-                .getPackageManager()
-                .queryIntentServicesAsUser(new Intent(CompanionDeviceService.SERVICE_INTERFACE),
-                        PackageManager.GET_META_DATA, a.getUserId());
-        List<ResolveInfo> packageResolveInfos = filter(resolveInfos,
-                info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName()));
-        List<BoundService> serviceConnectors = new ArrayList<>();
-        if (!validatePackageInfo(packageResolveInfos, a)) {
-            return serviceConnectors;
-        }
-        for (ResolveInfo packageResolveInfo : packageResolveInfos) {
-            boolean isPrimary = (packageResolveInfo.serviceInfo.metaData != null
-                    && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY))
-                    || packageResolveInfos.size() == 1;
-            ComponentName componentName = packageResolveInfo.serviceInfo.getComponentName();
-
-            Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
-
-            ServiceConnector<ICompanionDeviceService> serviceConnector =
-                    new ServiceConnector.Impl<ICompanionDeviceService>(context,
-                            new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(
-                                    componentName), BIND_IMPORTANT, a.getUserId(),
-                            ICompanionDeviceService.Stub::asInterface) {
-                        @Override
-                        protected long getAutoDisconnectTimeoutMs() {
-                            // Service binding is managed manually based on corresponding device
-                            // being nearby
-                            return -1;
-                        }
-
-                        @Override
-                        public void binderDied() {
-                            super.binderDied();
-                            if (a.isSelfManaged()) {
-                                mBoundServices.forUser(a.getUserId()).remove(a.getPackageName());
-                                mService.mPresentSelfManagedDevices.remove(a.getId());
-                            } else {
-                                // Re-connect to the service if process gets killed
-                                handler.postDelayed(
-                                        this::connect,
-                                        CompanionDeviceManagerService
-                                                .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
-                            }
-                        }
-                    };
-
-            serviceConnectors.add(new BoundService(componentName, isPrimary, serviceConnector));
-        }
-        return serviceConnectors;
-    }
-
-    private boolean validatePackageInfo(List<ResolveInfo> packageResolveInfos,
-            AssociationInfo association) {
-        if (packageResolveInfos.size() == 0 || packageResolveInfos.size() > 5) {
-            Slog.e(LOG_TAG, "Device presence listener package must have at least one and not "
-                    + "more than five CompanionDeviceService(s) declared. But "
-                    + association.getPackageName()
-                    + " has " + packageResolveInfos.size());
-            return false;
-        }
-
-        int primaryCount = 0;
-        for (ResolveInfo packageResolveInfo : packageResolveInfos) {
-            String servicePermission = packageResolveInfo.serviceInfo.permission;
-            if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) {
-                Slog.e(LOG_TAG, "Binding CompanionDeviceService must have "
-                        + BIND_COMPANION_DEVICE_SERVICE + " permission.");
-                return false;
-            }
-
-            if (packageResolveInfo.serviceInfo.metaData != null
-                    && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) {
-                primaryCount++;
-                if (primaryCount > 1) {
-                    Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
-                            + "to be bound but "
-                            + association.getPackageName() + "has " + primaryCount);
-                    return false;
-                }
-            }
-        }
-
-        if (packageResolveInfos.size() > 1 && primaryCount == 0) {
-            Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
-                    + "to be bound when declare more than one CompanionDeviceService but "
-                    + association.getPackageName() + " has " + primaryCount);
-            return false;
-        }
-
-        if (packageResolveInfos.size() == 1 && primaryCount != 0) {
-            Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one"
-                    + " CompanionDeviceService " + "but " + association.getPackageName()
-                    + " has " + primaryCount);
-        }
-
-        return true;
-    }
-
-    private static class BoundService {
-        private final ComponentName mComponentName;
-        private final boolean mIsPrimary;
-        private final ServiceConnector<ICompanionDeviceService> mServiceConnector;
-
-        BoundService(ComponentName componentName,
-                boolean isPrimary,  ServiceConnector<ICompanionDeviceService> serviceConnector) {
-            this.mComponentName = componentName;
-            this.mIsPrimary = isPrimary;
-            this.mServiceConnector = serviceConnector;
-        }
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index 777917c..f2a58b7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -17,6 +17,7 @@
 package com.android.server.companion;
 
 import static android.content.Context.BIND_IMPORTANT;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,10 +29,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Handler;
 import android.os.IBinder;
 import android.util.Log;
 
 import com.android.internal.infra.ServiceConnector;
+import com.android.server.ServiceThread;
 
 /**
  * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
@@ -106,6 +109,19 @@
         return ICompanionDeviceService.Stub.asInterface(service);
     }
 
+    /**
+     * Overrides {@link ServiceConnector.Impl#getJobHandler()} to provide an alternative Thread
+     * ("in form of" a {@link Handler}) to process jobs on.
+     * <p>
+     * (By default, {@link ServiceConnector.Impl} process jobs on the
+     * {@link android.os.Looper#getMainLooper() MainThread} which is a shared singleton thread
+     * within system_server and thus tends to get heavily congested)
+     */
+    @Override
+    protected @NonNull Handler getJobHandler() {
+        return getServiceThread().getThreadHandler();
+    }
+
     @Override
     protected long getAutoDisconnectTimeoutMs() {
         // Do NOT auto-disconnect.
@@ -116,4 +132,25 @@
         return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
                 .setComponent(componentName);
     }
+
+    private static @NonNull ServiceThread getServiceThread() {
+        if (sServiceThread == null) {
+            synchronized (CompanionDeviceManagerService.class) {
+                if (sServiceThread == null) {
+                    sServiceThread = new ServiceThread("companion-device-service-connector",
+                            THREAD_PRIORITY_DEFAULT, /* allowIo */ false);
+                    sServiceThread.start();
+                }
+            }
+        }
+        return sServiceThread;
+    }
+
+    /**
+     * A worker thread for the {@link ServiceConnector} to process jobs on.
+     *
+     * <p>
+     *  Do NOT reference directly, use {@link #getServiceThread()} method instead.
+     */
+    private static volatile @Nullable ServiceThread sServiceThread;
 }
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index fcb14a4..a2b2059 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -18,10 +18,9 @@
 
 import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
 import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
-import static android.content.pm.PackageManager.GET_META_DATA;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 
-import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+import static com.android.server.companion.CompanionDeviceManagerService.TAG;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -53,7 +52,8 @@
 final class PackageUtils {
     private static final Intent COMPANION_SERVICE_INTENT =
             new Intent(CompanionDeviceService.SERVICE_INTERFACE);
-    private static final String META_DATA_PRIMARY_TAG = "android.companion.primary";
+    private static final String PROPERTY_PRIMARY_TAG =
+            "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE";
 
     static @Nullable PackageInfo getPackageInfo(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName) {
@@ -84,9 +84,8 @@
     static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser(
             @NonNull Context context, @UserIdInt int userId) {
         final PackageManager pm = context.getPackageManager();
-        final ResolveInfoFlags flags = ResolveInfoFlags.of(GET_META_DATA);
-        final List<ResolveInfo> companionServices =
-                pm.queryIntentServicesAsUser(COMPANION_SERVICE_INTENT, flags, userId);
+        final List<ResolveInfo> companionServices = pm.queryIntentServicesAsUser(
+                COMPANION_SERVICE_INTENT, ResolveInfoFlags.of(0), userId);
 
         final Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>();
 
@@ -96,7 +95,7 @@
             final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE
                     .equals(resolveInfo.serviceInfo.permission);
             if (!requiresPermission) {
-                Slog.w(LOG_TAG, "CompanionDeviceService "
+                Slog.w(TAG, "CompanionDeviceService "
                         + service.getComponentName().flattenToShortString() + " must require "
                         + "android.permission.BIND_COMPANION_DEVICE_SERVICE");
                 continue;
@@ -109,7 +108,8 @@
                             service.packageName, it -> new LinkedList<>());
 
             final ComponentName componentName = service.getComponentName();
-            if (isPrimaryCompanionDeviceService(service)) {
+
+            if (isPrimaryCompanionDeviceService(pm, componentName)) {
                 // "Primary" service should be at the head of the list.
                 services.addFirst(componentName);
             } else {
@@ -120,7 +120,12 @@
         return packageNameToServiceInfoList;
     }
 
-    private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) {
-        return service.metaData != null && service.metaData.getBoolean(META_DATA_PRIMARY_TAG);
+    private static boolean isPrimaryCompanionDeviceService(@NonNull PackageManager pm,
+            @NonNull ComponentName componentName) {
+        try {
+            return pm.getProperty(PROPERTY_PRIMARY_TAG, componentName).getBoolean();
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
     }
 }
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 0e593e14..ac1bf1b 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -36,6 +36,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
 import android.content.Context;
@@ -190,6 +191,19 @@
         return checkCallerCanManageCompanionDevice(context);
     }
 
+    static @Nullable AssociationInfo sanitizeWithCallerChecks(@NonNull Context context,
+            @Nullable AssociationInfo association) {
+        if (association == null) return null;
+
+        final int userId = association.getUserId();
+        final String packageName = association.getPackageName();
+        if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) {
+            return null;
+        }
+
+        return association;
+    }
+
     private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
         try {
             return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
index 904283f..35488a8 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -19,6 +19,7 @@
 import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;
 
 import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
+import static com.android.server.companion.CompanionDeviceManagerService.TAG;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -35,7 +36,6 @@
 /** Utility methods for accessing {@link RoleManager} APIs. */
 @SuppressLint("LongLogTag")
 final class RolesUtils {
-    private static final String TAG = CompanionDeviceManagerService.LOG_TAG;
 
     static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
             @NonNull String packageName, @NonNull String role) {
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
new file mode 100644
index 0000000..2c42c91
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraInjectionSession;
+import android.hardware.camera2.CameraManager;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Handles blocking access to the camera for apps running on virtual devices.
+ */
+class CameraAccessController extends CameraManager.AvailabilityCallback {
+    private static final String TAG = "CameraAccessController";
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
+    CameraAccessBlockedCallback mBlockedCallback;
+    private CameraManager mCameraManager;
+    private boolean mListeningForCameraEvents;
+    private PackageManager mPackageManager;
+
+    @GuardedBy("mLock")
+    private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
+
+    static class InjectionSessionData {
+        public int appUid;
+        public ArrayMap<String, CameraInjectionSession> cameraIdToSession = new ArrayMap<>();
+    }
+
+    interface CameraAccessBlockedCallback {
+        /**
+         * Called whenever an app was blocked from accessing a camera.
+         * @param appUid uid for the app which was blocked
+         */
+        void onCameraAccessBlocked(int appUid);
+    }
+
+    CameraAccessController(Context context,
+            VirtualDeviceManagerInternal virtualDeviceManagerInternal,
+            CameraAccessBlockedCallback blockedCallback) {
+        mContext = context;
+        mVirtualDeviceManagerInternal = virtualDeviceManagerInternal;
+        mBlockedCallback = blockedCallback;
+        mCameraManager = mContext.getSystemService(CameraManager.class);
+        mPackageManager = mContext.getPackageManager();
+    }
+
+    /**
+     * Starts watching for camera access by uids running on a virtual device, if we were not
+     * already doing so.
+     */
+    public void startObservingIfNeeded() {
+        synchronized (mLock) {
+            if (!mListeningForCameraEvents) {
+                mCameraManager.registerAvailabilityCallback(mContext.getMainExecutor(), this);
+                mListeningForCameraEvents = true;
+            }
+        }
+    }
+
+    /**
+     * Stop watching for camera access.
+     */
+    public void stopObserving() {
+        synchronized (mLock) {
+            mCameraManager.unregisterAvailabilityCallback(this);
+            mListeningForCameraEvents = false;
+        }
+    }
+
+    @Override
+    public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
+        synchronized (mLock) {
+            try {
+                final ApplicationInfo ainfo =
+                        mPackageManager.getApplicationInfo(packageName, 0);
+                InjectionSessionData data = mPackageToSessionData.get(packageName);
+                if (!mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(ainfo.uid)) {
+                    CameraInjectionSession existingSession =
+                            (data != null) ? data.cameraIdToSession.get(cameraId) : null;
+                    if (existingSession != null) {
+                        existingSession.close();
+                        data.cameraIdToSession.remove(cameraId);
+                        if (data.cameraIdToSession.isEmpty()) {
+                            mPackageToSessionData.remove(packageName);
+                        }
+                    }
+                    return;
+                }
+                if (data == null) {
+                    data = new InjectionSessionData();
+                    data.appUid = ainfo.uid;
+                    mPackageToSessionData.put(packageName, data);
+                }
+                if (data.cameraIdToSession.containsKey(cameraId)) {
+                    return;
+                }
+                startBlocking(packageName, cameraId);
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.e(TAG, "onCameraOpened - unknown package " + packageName, e);
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void onCameraClosed(@NonNull String cameraId) {
+        synchronized (mLock) {
+            for (int i = mPackageToSessionData.size() - 1; i >= 0; i--) {
+                InjectionSessionData data = mPackageToSessionData.valueAt(i);
+                CameraInjectionSession session = data.cameraIdToSession.get(cameraId);
+                if (session != null) {
+                    session.close();
+                    data.cameraIdToSession.remove(cameraId);
+                    if (data.cameraIdToSession.isEmpty()) {
+                        mPackageToSessionData.removeAt(i);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Turns on blocking for a particular camera and package.
+     */
+    private void startBlocking(String packageName, String cameraId) {
+        try {
+            mCameraManager.injectCamera(packageName, cameraId, /* externalCamId */ "",
+                    mContext.getMainExecutor(),
+                    new CameraInjectionSession.InjectionStatusCallback() {
+                        @Override
+                        public void onInjectionSucceeded(
+                                @NonNull CameraInjectionSession session) {
+                            CameraAccessController.this.onInjectionSucceeded(cameraId, packageName,
+                                    session);
+                        }
+
+                        @Override
+                        public void onInjectionError(@NonNull int errorCode) {
+                            CameraAccessController.this.onInjectionError(cameraId, packageName,
+                                    errorCode);
+                        }
+                    });
+        } catch (CameraAccessException e) {
+            Slog.e(TAG,
+                    "Failed to injectCamera for cameraId:" + cameraId + " package:" + packageName,
+                    e);
+        }
+    }
+
+    private void onInjectionSucceeded(String cameraId, String packageName,
+            @NonNull CameraInjectionSession session) {
+        synchronized (mLock) {
+            InjectionSessionData data = mPackageToSessionData.get(packageName);
+            if (data == null) {
+                Slog.e(TAG, "onInjectionSucceeded didn't find expected entry for package "
+                        + packageName);
+                session.close();
+                return;
+            }
+            CameraInjectionSession existingSession = data.cameraIdToSession.put(cameraId, session);
+            if (existingSession != null) {
+                Slog.e(TAG, "onInjectionSucceeded found unexpected existing session for camera "
+                        + cameraId);
+                existingSession.close();
+            }
+        }
+    }
+
+    private void onInjectionError(String cameraId, String packageName, @NonNull int errorCode) {
+        if (errorCode != ERROR_INJECTION_UNSUPPORTED) {
+            // ERROR_INJECTION_UNSUPPORTED means that there wasn't an external camera to map to the
+            // internal camera, which is expected when using the injection interface as we are in
+            // this class to simply block camera access. Any other error is unexpected.
+            Slog.e(TAG, "Unexpected injection error code:" + errorCode + " for camera:" + cameraId
+                    + " and package:" + packageName);
+            return;
+        }
+        synchronized (mLock) {
+            InjectionSessionData data = mPackageToSessionData.get(packageName);
+            if (data != null) {
+                mBlockedCallback.onCameraAccessBlocked(data.appUid);
+            }
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index dafcc60..4afa96c 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -47,9 +47,17 @@
 /**
  * A controller to control the policies of the windows that can be displayed on the virtual display.
  */
-class GenericWindowPolicyController extends DisplayWindowPolicyController {
+public class GenericWindowPolicyController extends DisplayWindowPolicyController {
 
-    private static final String TAG = "VirtualDeviceManager";
+    private static final String TAG = "GenericWindowPolicyController";
+
+    /** Interface to listen running applications change on virtual display. */
+    public interface RunningAppsChangedListener {
+        /**
+         * Notifies the running applications change.
+         */
+        void onRunningAppsChanged(ArraySet<Integer> runningUids);
+    }
 
     private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT =
             new ComponentName("android", BlockedAppStreamingActivity.class.getName());
@@ -74,6 +82,9 @@
     @Nullable private final ActivityListener mActivityListener;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
+    @Nullable
+    private RunningAppsChangedListener mRunningAppsChangedListener;
+
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
      * device.
@@ -84,7 +95,7 @@
      * @param activityListener Activity listener to listen for activity changes. The display ID
      *   is not populated in this callback and is always {@link Display#INVALID_DISPLAY}.
      */
-    GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
+    public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
             @NonNull ArraySet<UserHandle> allowedUsers,
             @Nullable Set<ComponentName> allowedActivities,
             @Nullable Set<ComponentName> blockedActivities,
@@ -98,6 +109,11 @@
         mActivityListener = activityListener;
     }
 
+    /** Sets listener for running applications change. */
+    public void setRunningAppsChangedListener(@Nullable RunningAppsChangedListener listener) {
+        mRunningAppsChangedListener = listener;
+    }
+
     @Override
     public boolean canContainActivities(@NonNull List<ActivityInfo> activities) {
         // Can't display all the activities if any of them don't want to be displayed.
@@ -139,6 +155,9 @@
             // Post callback on the main thread so it doesn't block activity launching
             mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY));
         }
+        if (mRunningAppsChangedListener != null) {
+            mRunningAppsChangedListener.onRunningAppsChanged(runningUids);
+        }
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 075d96d..2f6a46a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -23,6 +23,8 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
@@ -32,6 +34,7 @@
 import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.audio.IAudioSessionCallback;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -55,11 +58,14 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.Display;
+import android.widget.Toast;
 import android.window.DisplayWindowPolicyController;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.server.companion.virtual.audio.VirtualAudioController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -78,6 +84,7 @@
     private final PendingTrampolineCallback mPendingTrampolineCallback;
     private final int mOwnerUid;
     private final InputController mInputController;
+    private VirtualAudioController mVirtualAudioController;
     @VisibleForTesting
     final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
     private final OnDeviceCloseListener mListener;
@@ -245,6 +252,46 @@
         close();
     }
 
+    @VisibleForTesting
+    VirtualAudioController getVirtualAudioControllerForTesting() {
+        return mVirtualAudioController;
+    }
+
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    @Override // Binder call
+    public void onAudioSessionStarting(int displayId, IAudioSessionCallback callback) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to start audio session");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)) {
+                throw new SecurityException(
+                        "Cannot start audio session for a display not associated with this virtual "
+                                + "device");
+            }
+
+            if (mVirtualAudioController == null) {
+                mVirtualAudioController = new VirtualAudioController(mContext);
+                GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
+                mVirtualAudioController.startListening(gwpc, callback);
+            }
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    @Override // Binder call
+    public void onAudioSessionEnded() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to stop audio session");
+        synchronized (mVirtualDeviceLock) {
+            if (mVirtualAudioController != null) {
+                mVirtualAudioController.stopListening();
+                mVirtualAudioController = null;
+            }
+        }
+    }
+
     @Override // Binder call
     public void createVirtualKeyboard(
             int displayId,
@@ -539,6 +586,26 @@
         return false;
     }
 
+    /**
+     * Shows a toast on virtual displays owned by this device which have a given uid running.
+     */
+    void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration) {
+        synchronized (mVirtualDeviceLock) {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            final int size = mWindowPolicyControllers.size();
+            for (int i = 0; i < size; i++) {
+                if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
+                    int displayId = mWindowPolicyControllers.keyAt(i);
+                    Display display = displayManager.getDisplay(displayId);
+                    if (display != null && display.isValid()) {
+                        Toast.makeText(mContext.createDisplayContext(display), resId,
+                                duration).show();
+                    }
+                }
+            }
+        }
+    }
+
     interface OnDeviceCloseListener {
         void onClose(int associationId);
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b507110..7842697 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -39,6 +39,7 @@
 import android.util.ExceptionUtils;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.widget.Toast;
 import android.window.DisplayWindowPolicyController;
 
 import com.android.internal.annotations.GuardedBy;
@@ -62,8 +63,10 @@
 
     private final Object mVirtualDeviceManagerLock = new Object();
     private final VirtualDeviceManagerImpl mImpl;
+    private VirtualDeviceManagerInternal mLocalService;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
+    private final CameraAccessController mCameraAccessController;
 
     /**
      * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
@@ -90,6 +93,9 @@
     public VirtualDeviceManagerService(Context context) {
         super(context);
         mImpl = new VirtualDeviceManagerImpl();
+        mLocalService = new LocalService();
+        mCameraAccessController = new CameraAccessController(getContext(), mLocalService,
+                this::onCameraAccessBlocked);
     }
 
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
@@ -118,8 +124,7 @@
     @Override
     public void onStart() {
         publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
-        publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
-
+        publishLocalService(VirtualDeviceManagerInternal.class, mLocalService);
         ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService(
                 ActivityTaskManagerInternal.class);
         activityTaskManagerInternal.registerActivityStartInterceptor(
@@ -169,6 +174,16 @@
         }
     }
 
+    void onCameraAccessBlocked(int appUid) {
+        synchronized (mVirtualDeviceManagerLock) {
+            int size = mVirtualDevices.size();
+            for (int i = 0; i < size; i++) {
+                mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
+                        com.android.internal.R.string.vdm_camera_access_denied, Toast.LENGTH_LONG);
+            }
+        }
+    }
+
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
             VirtualDeviceImpl.PendingTrampolineCallback {
 
@@ -205,10 +220,14 @@
                             public void onClose(int associationId) {
                                 synchronized (mVirtualDeviceManagerLock) {
                                     mVirtualDevices.remove(associationId);
+                                    if (mVirtualDevices.size() == 0) {
+                                        mCameraAccessController.stopObserving();
+                                    }
                                 }
                             }
                         },
                         this, activityListener, params);
+                mCameraAccessController.startObservingIfNeeded();
                 mVirtualDevices.put(associationInfo.getId(), virtualDevice);
                 return virtualDevice;
             }
diff --git a/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java b/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java
new file mode 100644
index 0000000..2d72913
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.audio;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+
+import java.util.List;
+
+/**
+ * Wrapper class for other classes to listen {@link #onPlaybackConfigChanged(List)} by implementing
+ * {@link AudioPlaybackCallback} instead of inheriting the
+ * {@link AudioManager.AudioPlaybackCallback}.
+ */
+final class AudioPlaybackDetector extends AudioManager.AudioPlaybackCallback {
+
+    /**
+     * Interface to listen {@link #onPlaybackConfigChanged(List)} from
+     * {@link AudioManager.AudioPlaybackCallback}.
+     */
+    interface AudioPlaybackCallback {
+        void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs);
+    }
+
+    private final AudioManager mAudioManager;
+    private AudioPlaybackCallback mAudioPlaybackCallback;
+
+    AudioPlaybackDetector(Context context) {
+        mAudioManager = context.getSystemService(AudioManager.class);
+    }
+
+    void register(@NonNull AudioPlaybackCallback callback) {
+        mAudioPlaybackCallback = callback;
+        mAudioManager.registerAudioPlaybackCallback(/* cb= */ this, /* handler= */ null);
+    }
+
+    void unregister() {
+        mAudioPlaybackCallback = null;
+        mAudioManager.unregisterAudioPlaybackCallback(/* cb= */ this);
+    }
+
+    @Override
+    public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+        super.onPlaybackConfigChanged(configs);
+        if (mAudioPlaybackCallback != null) {
+            mAudioPlaybackCallback.onPlaybackConfigChanged(configs);
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java b/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java
new file mode 100644
index 0000000..c204145
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.audio;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
+
+import java.util.List;
+
+/**
+ * Wrapper class for other classes to listen {@link #onRecordingConfigChanged(List)} by implementing
+ * {@link AudioRecordingCallback} instead of inheriting the
+ * {@link AudioManager.AudioRecordingCallback}.
+ */
+final class AudioRecordingDetector extends AudioManager.AudioRecordingCallback {
+
+    /**
+     * Interface to listen {@link #onRecordingConfigChanged(List)} from
+     * {@link AudioManager.AudioRecordingCallback}.
+     */
+    interface AudioRecordingCallback {
+        void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs);
+    }
+
+    private final AudioManager mAudioManager;
+    private AudioRecordingCallback mAudioRecordingCallback;
+
+    AudioRecordingDetector(Context context) {
+        mAudioManager = context.getSystemService(AudioManager.class);
+    }
+
+    void register(@NonNull AudioRecordingCallback callback) {
+        mAudioRecordingCallback = callback;
+        mAudioManager.registerAudioRecordingCallback(/* cb= */ this, /* handler= */ null);
+    }
+
+    void unregister() {
+        mAudioRecordingCallback = null;
+        mAudioManager.unregisterAudioRecordingCallback(/* cb= */ this);
+    }
+
+    @Override
+    public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+        super.onRecordingConfigChanged(configs);
+        if (mAudioRecordingCallback != null) {
+            mAudioRecordingCallback.onRecordingConfigChanged(configs);
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
new file mode 100644
index 0000000..1dc87d6
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.audio;
+
+import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.companion.virtual.audio.IAudioSessionCallback;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.companion.virtual.GenericWindowPolicyController;
+import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
+import com.android.server.companion.virtual.audio.AudioPlaybackDetector.AudioPlaybackCallback;
+import com.android.server.companion.virtual.audio.AudioRecordingDetector.AudioRecordingCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages audio streams associated with a {@link VirtualAudioDevice}. Responsible for monitoring
+ * running applications and playback configuration changes in order to correctly re-route audio and
+ * then notify clients of these changes.
+ */
+public final class VirtualAudioController implements AudioPlaybackCallback,
+        AudioRecordingCallback, RunningAppsChangedListener {
+    private static final String TAG = "VirtualAudioController";
+    private static final int UPDATE_REROUTING_APPS_DELAY_MS = 2000;
+
+    private final Context mContext;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Runnable mUpdateAudioRoutingRunnable = this::notifyAppsNeedingAudioRoutingChanged;
+    private final AudioPlaybackDetector mAudioPlaybackDetector;
+    private final AudioRecordingDetector mAudioRecordingDetector;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final ArraySet<Integer> mRunningAppUids = new ArraySet<>();
+    @GuardedBy("mLock")
+    private ArraySet<Integer> mPlayingAppUids = new ArraySet<>();
+    private GenericWindowPolicyController mGenericWindowPolicyController;
+    private final Object mCallbackLock = new Object();
+    @Nullable
+    @GuardedBy("mCallbackLock")
+    private IAudioSessionCallback mCallback;
+
+    public VirtualAudioController(Context context) {
+        mContext = context;
+        mAudioPlaybackDetector = new AudioPlaybackDetector(context);
+        mAudioRecordingDetector = new AudioRecordingDetector(context);
+    }
+
+    /**
+     * Starts to listen to running applications and audio configuration changes on virtual display
+     * for audio capture and injection.
+     */
+    public void startListening(
+            @NonNull GenericWindowPolicyController genericWindowPolicyController,
+            @Nullable IAudioSessionCallback callback) {
+        mGenericWindowPolicyController = genericWindowPolicyController;
+        mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ this);
+        synchronized (mCallbackLock) {
+            mCallback = callback;
+        }
+        synchronized (mLock) {
+            mRunningAppUids.clear();
+            mPlayingAppUids.clear();
+        }
+        mAudioPlaybackDetector.register(/* callback= */ this);
+        mAudioRecordingDetector.register(/* callback= */ this);
+    }
+
+    /**
+     * Stops listening to running applications and audio configuration changes on virtual display
+     * for audio capture and injection.
+     */
+    public void stopListening() {
+        if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) {
+            mHandler.removeCallbacks(mUpdateAudioRoutingRunnable);
+        }
+        mAudioPlaybackDetector.unregister();
+        mAudioRecordingDetector.unregister();
+        if (mGenericWindowPolicyController != null) {
+            mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ null);
+            mGenericWindowPolicyController = null;
+        }
+        synchronized (mCallbackLock) {
+            mCallback = null;
+        }
+    }
+
+    @Override
+    public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
+        synchronized (mLock) {
+            if (mRunningAppUids.equals(runningUids)) {
+                // Ignore no-op events.
+                return;
+            }
+            mRunningAppUids.clear();
+            mRunningAppUids.addAll(runningUids);
+
+            ArraySet<Integer> oldPlayingAppUids = mPlayingAppUids;
+
+            // Update the list of playing apps after caching the old list, and before checking if
+            // the list of playing apps is empty. This is a subset of the running apps, so we need
+            // to update this here as well.
+            AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+            List<AudioPlaybackConfiguration> configs =
+                    audioManager.getActivePlaybackConfigurations();
+            mPlayingAppUids = findPlayingAppUids(configs, mRunningAppUids);
+
+            // Do not change rerouted applications while any application is playing, or the sound
+            // will be leaked from phone during the transition. Delay the change until we detect
+            // there is no application is playing in onPlaybackConfigChanged().
+            if (!mPlayingAppUids.isEmpty()) {
+                Slog.i(TAG, "Audio is playing, do not change rerouted apps");
+                return;
+            }
+
+            // An application previously playing audio was removed from the display.
+            if (!oldPlayingAppUids.isEmpty()) {
+                // Delay changing the rerouted application when the last application playing audio
+                // was removed from virtual device, or the sound will be leaked from phone side
+                // during the transition.
+                Slog.i(TAG, "The last playing app removed, delay change rerouted apps");
+                if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) {
+                    mHandler.removeCallbacks(mUpdateAudioRoutingRunnable);
+                }
+                mHandler.postDelayed(mUpdateAudioRoutingRunnable, UPDATE_REROUTING_APPS_DELAY_MS);
+                return;
+            }
+        }
+
+        // Normal case with no application playing, just update routing.
+        notifyAppsNeedingAudioRoutingChanged();
+    }
+
+    @Override
+    public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+        updatePlayingApplications(configs);
+
+        List<AudioPlaybackConfiguration> audioPlaybackConfigurations;
+        synchronized (mLock) {
+            // Filter configurations of applications running on virtual device.
+            audioPlaybackConfigurations = findPlaybackConfigurations(configs, mRunningAppUids);
+        }
+        synchronized (mCallbackLock) {
+            if (mCallback != null) {
+                try {
+                    mCallback.onPlaybackConfigChanged(audioPlaybackConfigurations);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "RemoteException when calling onPlaybackConfigChanged", e);
+                }
+            }
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Override
+    public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+        List<AudioRecordingConfiguration> audioRecordingConfigurations;
+        synchronized (mLock) {
+            // Filter configurations of applications running on virtual device.
+            audioRecordingConfigurations = findRecordingConfigurations(configs, mRunningAppUids);
+        }
+        synchronized (mCallbackLock) {
+            if (mCallback != null) {
+                try {
+                    mCallback.onRecordingConfigChanged(audioRecordingConfigurations);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "RemoteException when calling onRecordingConfigChanged", e);
+                }
+            }
+        }
+    }
+
+    private void updatePlayingApplications(List<AudioPlaybackConfiguration> configs) {
+        synchronized (mLock) {
+            ArraySet<Integer> playingAppUids = findPlayingAppUids(configs, mRunningAppUids);
+            if (mPlayingAppUids.equals(playingAppUids)) {
+                return;
+            }
+            mPlayingAppUids = playingAppUids;
+        }
+
+        // Updated rerouted apps, even if the app is already playing. It originally should be done
+        // when onRunningAppsChanged() is called, but we don't want to interrupt the audio
+        // streaming and cause the sound leak from phone when it's playing, so delay until here.
+        notifyAppsNeedingAudioRoutingChanged();
+    }
+
+    private void notifyAppsNeedingAudioRoutingChanged() {
+        if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) {
+            mHandler.removeCallbacks(mUpdateAudioRoutingRunnable);
+        }
+
+        int[] runningUids;
+        synchronized (mLock) {
+            runningUids = new int[mRunningAppUids.size()];
+            for (int i = 0; i < mRunningAppUids.size(); i++) {
+                runningUids[i] = mRunningAppUids.valueAt(i);
+            }
+        }
+
+        synchronized (mCallbackLock) {
+            if (mCallback != null) {
+                try {
+                    mCallback.onAppsNeedingAudioRoutingChanged(runningUids);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "RemoteException when calling updateReroutingApps", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds uid of playing applications from the given running applications.
+     *
+     * @param configs a list of playback configs which get from {@link AudioManager}
+     */
+    private static ArraySet<Integer> findPlayingAppUids(List<AudioPlaybackConfiguration> configs,
+            ArraySet<Integer> runningAppUids) {
+        ArraySet<Integer> playingAppUids = new ArraySet<>();
+        for (AudioPlaybackConfiguration config : configs) {
+            if (runningAppUids.contains(config.getClientUid())
+                    && config.getPlayerState() == PLAYER_STATE_STARTED) {
+                playingAppUids.add(config.getClientUid());
+            }
+        }
+        return playingAppUids;
+    }
+
+    /** Finds a list of {@link AudioPlaybackConfiguration} for the given running applications. */
+    private static List<AudioPlaybackConfiguration> findPlaybackConfigurations(
+            List<AudioPlaybackConfiguration> configs,
+            ArraySet<Integer> runningAppUids) {
+        List<AudioPlaybackConfiguration> runningConfigs = new ArrayList<>();
+        for (AudioPlaybackConfiguration config : configs) {
+            if (runningAppUids.contains(config.getClientUid())) {
+                runningConfigs.add(config);
+            }
+        }
+        return runningConfigs;
+    }
+
+    /** Finds a list of {@link AudioRecordingConfiguration} for the given running applications. */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    private static List<AudioRecordingConfiguration> findRecordingConfigurations(
+            List<AudioRecordingConfiguration> configs, ArraySet<Integer> runningAppUids) {
+        List<AudioRecordingConfiguration> runningConfigs = new ArrayList<>();
+        for (AudioRecordingConfiguration config : configs) {
+            if (runningAppUids.contains(config.getClientUid())) {
+                runningConfigs.add(config);
+            }
+        }
+        return runningConfigs;
+    }
+
+    @VisibleForTesting
+    boolean hasPendingRunnable() {
+        return mHandler.hasCallbacks(mUpdateAudioRoutingRunnable);
+    }
+
+    @VisibleForTesting
+    void addPlayingAppsForTesting(int appUid) {
+        synchronized (mLock) {
+            mPlayingAppUids.add(appUid);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 91d2f55..6c220f6 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -45,6 +45,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
 /**
@@ -369,10 +370,13 @@
 
         // we are only interested in doing things at PHASE_BOOT_COMPLETED
         if (phase == PHASE_BOOT_COMPLETED) {
-            // due to potentially long computation that holds up boot time, apex sha computations
-            // are deferred to first call
             Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
             getVBMetaDigestInformation();
+
+            // due to potentially long computation that may hold up boot time, SHA256 computations
+            // for APEXs and Modules will be executed via threads.
+            Slog.i(TAG, "Executing APEX & Module digest computations");
+            computeApexAndModuleDigests();
         }
     }
 
@@ -382,6 +386,12 @@
         FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
     }
 
+    private void computeApexAndModuleDigests() {
+        // using Executors will allow the computations to be done asynchronously, thus not holding
+        // up boot time.
+        Executors.defaultThreadFactory().newThread(() -> updateBinaryMeasurements()).start();
+    }
+
     @NonNull
     private List<PackageInfo> getInstalledApexs() {
         List<PackageInfo> results = new ArrayList<PackageInfo>();
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index a0575cf..26d76a84 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -51,15 +51,12 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.regex.Matcher;
@@ -128,11 +125,9 @@
     // Location of ftrace pipe for notifications from kernel memory tools like KFENCE and KASAN.
     private static final String ERROR_REPORT_TRACE_PIPE =
             "/sys/kernel/tracing/instances/bootreceiver/trace_pipe";
-    // Stop after sending this many reports. See http://b/182159975.
+    // Stop after sending too many reports. See http://b/182159975.
     private static final int MAX_ERROR_REPORTS = 8;
     private static int sSentReports = 0;
-    // Avoid reporing the same bug from processDmesg() twice.
-    private static String sLastReportedBug = null;
 
     @Override
     public void onReceive(final Context context, Intent intent) {
@@ -175,7 +170,8 @@
          * We read from /sys/kernel/tracing/instances/bootreceiver/trace_pipe (set up by the
          * system), which will print an ftrace event when a memory corruption is detected in the
          * kernel.
-         * When an error is detected, we run the dmesg shell command and process its output.
+         * When an error is detected, we set the dmesg.start system property to notify dmesgd
+         * about a new error.
          */
         OnFileDescriptorEventListener traceCallback = new OnFileDescriptorEventListener() {
             final int mBufferSize = 1024;
@@ -191,8 +187,7 @@
                  * line, but to be on the safe side we keep reading until the buffer
                  * contains a '\n' character. In the unlikely case of a very buggy kernel
                  * the buffer may contain multiple tracing events that cannot be attributed
-                 * to particular error reports. In that case the latest error report
-                 * residing in dmesg is picked.
+                 * to particular error reports. dmesgd will take care of all errors.
                  */
                 try {
                     int nbytes = Os.read(fd, mTraceBuffer, 0, mBufferSize);
@@ -201,10 +196,13 @@
                         if (readStr.indexOf("\n") == -1) {
                             return OnFileDescriptorEventListener.EVENT_INPUT;
                         }
-                        processDmesg(context);
+                        if (sSentReports < MAX_ERROR_REPORTS) {
+                            SystemProperties.set("dmesgd.start", "1");
+                            sSentReports++;
+                        }
                     }
                 } catch (Exception e) {
-                    Slog.wtf(TAG, "Error processing dmesg output", e);
+                    Slog.wtf(TAG, "Error watching for trace events", e);
                     return 0;  // Unregister the handler.
                 }
                 return OnFileDescriptorEventListener.EVENT_INPUT;
@@ -216,157 +214,6 @@
 
     }
 
-    /**
-     * Check whether it is safe to collect this dmesg line or not.
-     *
-     * We only consider lines belonging to KASAN or KFENCE reports, but those may still contain
-     * user information, namely the process name:
-     *
-     *   [   69.547684] [ T6006]c7   6006  CPU: 7 PID: 6006 Comm: sh Tainted: G S       C O      ...
-     *
-     * hardware information:
-     *
-     *   [   69.558923] [ T6006]c7   6006  Hardware name: <REDACTED>
-     *
-     * or register dump (in KASAN reports only):
-     *
-     *   ... RIP: 0033:0x7f96443109da
-     *   ... RSP: 002b:00007ffcf0b51b08 EFLAGS: 00000202 ORIG_RAX: 00000000000000af
-     *   ... RAX: ffffffffffffffda RBX: 000055dc3ee521a0 RCX: 00007f96443109da
-     *
-     * (on x86_64)
-     *
-     *   ... pc : lpm_cpuidle_enter+0x258/0x384
-     *   ... lr : lpm_cpuidle_enter+0x1d4/0x384
-     *   ... sp : ffffff800820bea0
-     *   ... x29: ffffff800820bea0 x28: ffffffc2305f3ce0
-     *   ... ...
-     *   ... x9 : 0000000000000001 x8 : 0000000000000000
-     * (on ARM64)
-     *
-     * We therefore omit the lines that contain "Comm:", "Hardware name:", or match the general
-     * purpose register regexp.
-     *
-     * @param  line single line of `dmesg` output.
-     * @return      updated line with sensitive data removed, or null if the line must be skipped.
-     */
-    public static String stripSensitiveData(String line) {
-        /*
-         * General purpose register names begin with "R" on x86_64 and "x" on ARM64. The letter is
-         * followed by two symbols (numbers, letters or spaces) and a colon, which is followed by a
-         * 16-digit hex number. The optional "_" prefix accounts for ORIG_RAX on x86.
-         */
-        final String registerRegex = "[ _][Rx]..: [0-9a-f]{16}";
-        final Pattern registerPattern = Pattern.compile(registerRegex);
-
-        final String corruptionRegex = "Detected corrupted memory at 0x[0-9a-f]+";
-        final Pattern corruptionPattern = Pattern.compile(corruptionRegex);
-
-        if (line.contains("Comm: ") || line.contains("Hardware name: ")) return null;
-        if (registerPattern.matcher(line).find()) return null;
-
-        Matcher cm = corruptionPattern.matcher(line);
-        if (cm.find()) return cm.group(0);
-        return line;
-    }
-
-    /*
-     * Search dmesg output for the last error report from KFENCE or KASAN and copy it to Dropbox.
-     *
-     * Example report printed by the kernel (redacted to fit into 100 column limit):
-     *   [   69.236673] [ T6006]c7   6006  =========================================================
-     *   [   69.245688] [ T6006]c7   6006  BUG: KFENCE: out-of-bounds in kfence_handle_page_fault
-     *   [   69.245688] [ T6006]c7   6006
-     *   [   69.257816] [ T6006]c7   6006  Out-of-bounds access at 0xffffffca75c45000 (...)
-     *   [   69.267102] [ T6006]c7   6006   kfence_handle_page_fault+0x1bc/0x208
-     *   [   69.273536] [ T6006]c7   6006   __do_kernel_fault+0xa8/0x11c
-     *   ...
-     *   [   69.355427] [ T6006]c7   6006  kfence-#2 [0xffffffca75c46f30-0xffffffca75c46fff, ...
-     *   [   69.366938] [ T6006]c7   6006   __d_alloc+0x3c/0x1b4
-     *   [   69.371946] [ T6006]c7   6006   d_alloc_parallel+0x48/0x538
-     *   [   69.377578] [ T6006]c7   6006   __lookup_slow+0x60/0x15c
-     *   ...
-     *   [   69.547684] [ T6006]c7   6006  CPU: 7 PID: 6006 Comm: sh Tainted: G S       C O      ...
-     *   [   69.558923] [ T6006]c7   6006  Hardware name: <REDACTED>
-     *   [   69.567059] [ T6006]c7   6006  =========================================================
-     *
-     *   We rely on the kernel printing task/CPU ID for every log line (CONFIG_PRINTK_CALLER=y).
-     *   E.g. for the above report the task ID is T6006. Report lines may interleave with lines
-     *   printed by other kernel tasks, which will have different task IDs, so in order to collect
-     *   the report we:
-     *    - find the next occurrence of the "BUG: " line in the kernel log, parse it to obtain the
-     *      task ID and the tool name;
-     *    - scan the rest of dmesg output and pick every line that has the same task ID, until we
-     *      encounter a horizontal ruler, i.e.:
-     *      [   69.567059] [ T6006]c7   6006  ======================================================
-     *    - add that line to the error report, unless it contains sensitive information (see
-     *      logLinePotentiallySensitive())
-     *    - repeat the above steps till the last report is found.
-     */
-    private void processDmesg(Context ctx) throws IOException {
-        if (sSentReports == MAX_ERROR_REPORTS) return;
-        /*
-         * Only SYSTEM_KASAN_ERROR_REPORT and SYSTEM_KFENCE_ERROR_REPORT are supported at the
-         * moment.
-         */
-        final String[] bugTypes = new String[] { "KASAN", "KFENCE" };
-        final String tsRegex = "^\\[[^]]+\\] ";
-        final String bugRegex =
-                tsRegex + "\\[([^]]+)\\].*BUG: (" + String.join("|", bugTypes) + "):";
-        final Pattern bugPattern = Pattern.compile(bugRegex);
-
-        Process p = new ProcessBuilder("/system/bin/timeout", "-k", "90s", "60s",
-                                       "dmesg").redirectErrorStream(true).start();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
-        String line = null;
-        String task = null;
-        String tool = null;
-        String bugTitle = null;
-        Pattern reportPattern = null;
-        ArrayList<String> currentReport = null;
-        String lastReport = null;
-
-        while ((line = reader.readLine()) != null) {
-            if (currentReport == null) {
-                Matcher bm = bugPattern.matcher(line);
-                if (!bm.find()) continue;
-                task = bm.group(1);
-                tool = bm.group(2);
-                bugTitle = line;
-                currentReport = new ArrayList<String>();
-                currentReport.add(line);
-                String reportRegex = tsRegex + "\\[" + task + "\\].*";
-                reportPattern = Pattern.compile(reportRegex);
-                continue;
-            }
-            Matcher rm = reportPattern.matcher(line);
-            if (!rm.matches()) continue;
-            if ((line = stripSensitiveData(line)) == null) continue;
-            if (line.contains("================================")) {
-                lastReport = String.join("\n", currentReport);
-                currentReport = null;
-                continue;
-            }
-            currentReport.add(line);
-        }
-        if (lastReport == null) {
-            Slog.w(TAG, "Could not find report in dmesg.");
-            return;
-        }
-
-        // Avoid sending the same bug report twice.
-        if (bugTitle.equals(sLastReportedBug)) return;
-
-        final String reportTag = "SYSTEM_" + tool + "_ERROR_REPORT";
-        final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
-        final String headers = getCurrentBootHeaders();
-        final String reportText = headers + lastReport;
-
-        addTextToDropBox(db, reportTag, reportText, "/dev/kmsg", LOG_SIZE);
-        sLastReportedBug = bugTitle;
-        sSentReports++;
-    }
-
     private void removeOldUpdatePackages(Context context) {
         Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
     }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 39ac5ef..b59cd4c 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
 import static android.Manifest.permission.SHUTDOWN;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
@@ -30,6 +31,7 @@
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -206,6 +208,11 @@
      */
     @GuardedBy("mRulesLock")
     private SparseIntArray mUidFirewallRestrictedRules = new SparseIntArray();
+    /**
+     * Contains the per-UID firewall rules that are used when Low Power Standby is enabled.
+     */
+    @GuardedBy("mRulesLock")
+    private SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mRulesLock")
     final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -506,12 +513,14 @@
             syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable ");
             syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
             syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
 
             final int[] chains = {
                     FIREWALL_CHAIN_STANDBY,
                     FIREWALL_CHAIN_DOZABLE,
                     FIREWALL_CHAIN_POWERSAVE,
-                    FIREWALL_CHAIN_RESTRICTED
+                    FIREWALL_CHAIN_RESTRICTED,
+                    FIREWALL_CHAIN_LOW_POWER_STANDBY
             };
 
             for (int chain : chains) {
@@ -1438,6 +1447,8 @@
                 return FIREWALL_CHAIN_NAME_POWERSAVE;
             case FIREWALL_CHAIN_RESTRICTED:
                 return FIREWALL_CHAIN_NAME_RESTRICTED;
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
             default:
                 throw new IllegalArgumentException("Bad child chain: " + chain);
         }
@@ -1453,6 +1464,8 @@
                 return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_RESTRICTED:
                 return FIREWALL_ALLOWLIST;
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return FIREWALL_ALLOWLIST;
             default:
                 return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
         }
@@ -1571,6 +1584,8 @@
                 return mUidFirewallPowerSaveRules;
             case FIREWALL_CHAIN_RESTRICTED:
                 return mUidFirewallRestrictedRules;
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return mUidFirewallLowPowerStandbyRules;
             case FIREWALL_CHAIN_NONE:
                 return mUidFirewallRules;
             default:
@@ -1626,6 +1641,11 @@
             pw.println(getFirewallChainState(FIREWALL_CHAIN_RESTRICTED));
             dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_RESTRICTED,
                     mUidFirewallRestrictedRules);
+
+            pw.print("UID firewall low power standby chain enabled: ");
+            pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY));
+            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY,
+                    mUidFirewallLowPowerStandbyRules);
         }
 
         pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1749,6 +1769,11 @@
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of restricted mode");
                 return true;
             }
+            if (getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY)
+                    && mUidFirewallLowPowerStandbyRules.get(uid) != FIREWALL_RULE_ALLOW) {
+                if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby");
+                return true;
+            }
             if (mUidRejectOnMetered.get(uid)) {
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
                         + " in the background");
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 178b666..454028d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -872,13 +872,13 @@
                     break;
                 }
                 case H_COMPLETE_UNLOCK_USER: {
-                    completeUnlockUser((int) msg.obj);
+                    completeUnlockUser(msg.arg1);
                     break;
                 }
                 case H_VOLUME_STATE_CHANGED: {
                     final SomeArgs args = (SomeArgs) msg.obj;
-                    onVolumeStateChangedAsync((VolumeInfo) args.arg1, (int) args.arg2,
-                            (int) args.arg3);
+                    onVolumeStateChangedAsync((VolumeInfo) args.arg1, args.argi1, args.argi2);
+                    args.recycle();
                 }
             }
         }
@@ -1205,7 +1205,8 @@
             Slog.w(TAG, "UNLOCK_USER lost from vold reset, will retry, user:" + userId);
             mVold.onUserStarted(userId);
             mStoraged.onUserStarted(userId);
-            mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget();
+            mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId, /* arg2 (unusued) */ 0)
+                    .sendToTarget();
         }
     }
 
@@ -1263,7 +1264,8 @@
             Slog.wtf(TAG, e);
         }
 
-        mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget();
+        mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId, /* arg2 (unusued) */ 0)
+                .sendToTarget();
         if (mRemountCurrentUserVolumesOnUnlock && userId == mCurrentUserId) {
             maybeRemountVolumes(userId);
             mRemountCurrentUserVolumesOnUnlock = false;
@@ -1493,18 +1495,17 @@
         }
 
         @Override
-        public void onVolumeStateChanged(String volId, int state) {
+        public void onVolumeStateChanged(String volId, final int newState) {
             synchronized (mLock) {
                 final VolumeInfo vol = mVolumes.get(volId);
                 if (vol != null) {
                     final int oldState = vol.state;
-                    final int newState = state;
                     vol.state = newState;
                     final VolumeInfo vInfo = new VolumeInfo(vol);
                     final SomeArgs args = SomeArgs.obtain();
                     args.arg1 = vInfo;
-                    args.arg2 = oldState;
-                    args.arg3 = newState;
+                    args.argi1 = oldState;
+                    args.argi2 = newState;
                     mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
                     onVolumeStateChangedLocked(vInfo, oldState, newState);
                 }
@@ -4674,7 +4675,7 @@
     private int getMountModeInternal(int uid, String packageName) {
         try {
             // Get some easy cases out of the way first
-            if (Process.isIsolated(uid)) {
+            if (Process.isIsolated(uid) || Process.isSupplemental(uid)) {
                 return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
             }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dc64d80..092172a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2721,8 +2721,8 @@
 
     int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, final IServiceConnection connection, int flags,
-            String instanceName, boolean isSupplementalProcessService, String callingPackage,
-            final int userId)
+            String instanceName, boolean isSupplementalProcessService, int supplementedAppUid,
+            String callingPackage, final int userId)
             throws TransactionTooLargeException {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
                 + " type=" + resolvedType + " conn=" + connection.asBinder()
@@ -2807,8 +2807,8 @@
         final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
 
         ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
-                isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid,
-                userId, true, callerFg, isBindExternal, allowInstant);
+                isSupplementalProcessService, supplementedAppUid, resolvedType, callingPackage,
+                callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant);
         if (res == null) {
             return 0;
         }
@@ -3228,13 +3228,14 @@
             int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
             boolean allowInstant) {
-        return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage,
+        return retrieveServiceLocked(service, instanceName, false, 0, resolvedType, callingPackage,
                 callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
                 allowInstant);
     }
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
-            String instanceName, boolean isSupplementalProcessService, String resolvedType,
+            String instanceName, boolean isSupplementalProcessService, int supplementedAppUid,
+            String resolvedType,
             String callingPackage, int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
             boolean allowInstant) {
@@ -3415,7 +3416,7 @@
                                                                                   : null;
                     r = new ServiceRecord(mAm, className, name, definingPackageName,
                             definingUid, filter, sInfo, callingFromFg, res,
-                            supplementalProcessName);
+                            supplementalProcessName, supplementedAppUid);
                     res.setService(r);
                     smap.mServicesByInstanceName.put(name, r);
                     smap.mServicesByIntent.put(filter, r);
@@ -4189,8 +4190,16 @@
         if (app == null && !permissionsReviewRequired && !packageFrozen) {
             // TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service
             //  was initiated from a notification tap or not.
-            if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
-                        hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated)) == null) {
+            if (r.supplemental) {
+                final int uid = Process.toSupplementalUid(r.supplementedAppUid);
+                app = mAm.startSupplementalProcessLocked(procName, r.appInfo, true, intentFlags,
+                        hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, uid);
+                r.isolationHostProc = app;
+            } else {
+                app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
+                        hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated);
+            }
+            if (app == null) {
                 String msg = "Unable to launch app "
                         + r.appInfo.packageName + "/"
                         + r.appInfo.uid + " for service "
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7f64c00..a478c31 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -458,7 +458,7 @@
      * broadcasts
      */
     private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT =
-            SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", true);
+            SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false);
 
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
     static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
@@ -1892,6 +1892,8 @@
                 ProcessRecord app = mProcessList.newProcessRecordLocked(info, info.processName,
                         false,
                         0,
+                        false,
+                        0,
                         new HostingRecord("system"));
                 app.setPersistent(true);
                 app.setPid(MY_PID);
@@ -2780,18 +2782,32 @@
                     false /* knownToBeDead */, 0 /* intentFlags */,
                     sNullHostingRecord  /* hostingRecord */, ZYGOTE_POLICY_FLAG_EMPTY,
                     true /* allowWhileBooting */, true /* isolated */,
-                    uid, abiOverride, entryPoint, entryPointArgs, crashHandler);
+                    uid, false /* supplemental */, 0 /* supplementalUid */,
+                    abiOverride, entryPoint, entryPointArgs, crashHandler);
             return proc != null;
         }
     }
 
     @GuardedBy("this")
+    final ProcessRecord startSupplementalProcessLocked(String processName,
+            ApplicationInfo info, boolean knownToBeDead, int intentFlags,
+            HostingRecord hostingRecord, int zygotePolicyFlags, int supplementalUid) {
+        return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
+                hostingRecord, zygotePolicyFlags, false /* allowWhileBooting */,
+                false /* isolated */, 0 /* isolatedUid */,
+                true /* supplemental */, supplementalUid,
+                null /* ABI override */, null /* entryPoint */,
+                null /* entryPointArgs */, null /* crashHandler */);
+    }
+
+    @GuardedBy("this")
     final ProcessRecord startProcessLocked(String processName,
             ApplicationInfo info, boolean knownToBeDead, int intentFlags,
             HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting,
             boolean isolated) {
         return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
                 hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,
+                false /* supplemental */, 0 /* supplementalUid */,
                 null /* ABI override */, null /* entryPoint */,
                 null /* entryPointArgs */, null /* crashHandler */);
     }
@@ -6521,6 +6537,7 @@
 
         if (app == null) {
             app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0,
+                    false, 0,
                     new HostingRecord("added application",
                         customProcess != null ? customProcess : info.processName));
             updateLruProcessLocked(app, false, null);
@@ -12346,12 +12363,13 @@
             String resolvedType, IServiceConnection connection, int flags, String instanceName,
             String callingPackage, int userId) throws TransactionTooLargeException {
         return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
-                instanceName, false, callingPackage, userId);
+                instanceName, false, 0, callingPackage, userId);
     }
 
     private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
             String resolvedType, IServiceConnection connection, int flags, String instanceName,
-            boolean isSupplementalProcessService, String callingPackage, int userId)
+            boolean isSupplementalProcessService, int supplementedAppUid, String callingPackage,
+            int userId)
             throws TransactionTooLargeException {
         enforceNotIsolatedCaller("bindService");
 
@@ -12382,7 +12400,8 @@
 
         synchronized(this) {
             return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
-                    flags, instanceName, isSupplementalProcessService, callingPackage, userId);
+                    flags, instanceName, isSupplementalProcessService, supplementedAppUid,
+                    callingPackage, userId);
         }
     }
 
@@ -15976,8 +15995,8 @@
             return ActivityManagerService.this.bindServiceInstance(
                     mContext.getIApplicationThread(), mContext.getActivityToken(), service,
                     service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags,
-                    processName, /*isSupplementalProcessService*/ true, mContext.getOpPackageName(),
-                    UserHandle.getUserId(userAppUid)) != 0;
+                    processName, /*isSupplementalProcessService*/ true, userAppUid,
+                    mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0;
         }
 
         @Override
@@ -16121,6 +16140,23 @@
         }
 
         /**
+         * Returns package name by pid.
+         */
+        @Override
+        @Nullable
+        public String getPackageNameByPid(int pid) {
+            synchronized (mPidsSelfLocked) {
+                final ProcessRecord app = mPidsSelfLocked.get(pid);
+
+                if (app != null && app.info != null) {
+                    return app.info.packageName;
+                }
+
+                return null;
+            }
+        }
+
+        /**
          * Sets if the given pid has an overlay UI or not.
          *
          * @param pid The pid we are setting overlay UI for.
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 97727b08..08508b2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -31,6 +31,7 @@
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT;
 import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
 
 import android.app.ActivityManager;
@@ -3256,12 +3257,12 @@
             return -1;
         }
         if (arg == null) {
-            batteryTracker.mDebugUidPercentages.clear();
+            batteryTracker.clearDebugUidPercentage();
             return 0;
         }
         String[] pairs = arg.split(",");
         int[] uids = new int[pairs.length];
-        double[] values = new double[pairs.length];
+        double[][] values = new double[pairs.length][];
         try {
             for (int i = 0; i < pairs.length; i++) {
                 String[] pair = pairs[i].split("=");
@@ -3270,16 +3271,21 @@
                     return -1;
                 }
                 uids[i] = Integer.parseInt(pair[0]);
-                values[i] = Double.parseDouble(pair[1]);
+                final String[] vals = pair[1].split(":");
+                if (vals.length != BATTERY_USAGE_COUNT) {
+                    getErrPrintWriter().println("Malformed input");
+                    return -1;
+                }
+                values[i] = new double[vals.length];
+                for (int j = 0; j < vals.length; j++) {
+                    values[i][j] = Double.parseDouble(vals[j]);
+                }
             }
         } catch (NumberFormatException e) {
             getErrPrintWriter().println("Malformed input");
             return -1;
         }
-        batteryTracker.mDebugUidPercentages.clear();
-        for (int i = 0; i < pairs.length; i++) {
-            batteryTracker.mDebugUidPercentages.put(uids[i], values[i]);
-        }
+        batteryTracker.setDebugUidPercentage(uids, values);
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
index 75de3a1..3c780aa 100644
--- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppBatteryTracker.BATTERY_USAGE_NONE;
 import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
 import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_NUM;
 
@@ -28,10 +29,13 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
 import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates;
 import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppBatteryTracker.BatteryUsage;
+import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage;
 import com.android.server.am.BaseAppStateDurationsTracker.EventListener;
 import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
 import com.android.server.am.BaseAppStateTracker.Injector;
@@ -97,7 +101,8 @@
         if (!mInjector.getPolicy().isEnabled()) {
             return;
         }
-        final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid);
+        final ImmutableBatteryUsage batteryUsage = mAppRestrictionController
+                .getUidBatteryUsage(uid);
         synchronized (mLock) {
             UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME);
             if (pkg == null) {
@@ -120,22 +125,23 @@
      * @return The to-be-exempted battery usage of the given UID in the given duration; it could
      *         be considered as "exempted" due to various use cases, i.e. media playback.
      */
-    double getUidBatteryExemptedUsageSince(int uid, long since, long now) {
+    ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now) {
         if (!mInjector.getPolicy().isEnabled()) {
-            return 0.0d;
+            return BATTERY_USAGE_NONE;
         }
-        Pair<Double, Double> result;
+        Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> result;
         synchronized (mLock) {
             final UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME);
             if (pkg == null) {
-                return 0.0d;
+                return BATTERY_USAGE_NONE;
             }
             result = pkg.getBatteryUsageSince(since, now);
         }
-        if (result.second > 0.0d) {
+        if (!result.second.isEmpty()) {
             // We have an open event (just start, no stop), get the battery usage till now.
-            final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid);
-            return result.first + batteryUsage - result.second;
+            final ImmutableBatteryUsage batteryUsage = mAppRestrictionController
+                    .getUidBatteryUsage(uid);
+            return result.first.mutate().add(batteryUsage).subtract(result.second).unmutate();
         }
         return result.first;
     }
@@ -156,7 +162,7 @@
          * @param batteryUsage The background current drain since the system boots.
          * @param eventType One of EVENT_TYPE_* defined in the class BaseAppStateDurationsTracker.
          */
-        void addEvent(boolean start, long now, double batteryUsage, int eventType) {
+        void addEvent(boolean start, long now, ImmutableBatteryUsage batteryUsage, int eventType) {
             if (start) {
                 addEvent(start, new UidStateEventWithBattery(start, now, batteryUsage, null),
                         eventType);
@@ -169,7 +175,8 @@
                     return;
                 }
                 addEvent(start, new UidStateEventWithBattery(start, now,
-                        batteryUsage - last.getBatteryUsage(), last), eventType);
+                        batteryUsage.mutate().subtract(last.getBatteryUsage()).unmutate(), last),
+                        eventType);
             }
         }
 
@@ -183,34 +190,37 @@
          *         the second value is the battery usage since the system boots, if there is
          *         an open event(just start, no stop) at the end of the duration.
          */
-        Pair<Double, Double> getBatteryUsageSince(long since, long now, int eventType) {
+        Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since,
+                long now, int eventType) {
             return getBatteryUsageSince(since, now, mEvents[eventType]);
         }
 
-        private Pair<Double, Double> getBatteryUsageSince(long since, long now,
-                LinkedList<UidStateEventWithBattery> events) {
+        private Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since,
+                long now, LinkedList<UidStateEventWithBattery> events) {
             if (events == null || events.size() == 0) {
-                return Pair.create(0.0d, 0.0d);
+                return Pair.create(BATTERY_USAGE_NONE, BATTERY_USAGE_NONE);
             }
-            double batteryUsage = 0.0d;
+            final BatteryUsage batteryUsage = new BatteryUsage();
             UidStateEventWithBattery lastEvent = null;
             for (UidStateEventWithBattery event : events) {
                 lastEvent = event;
                 if (event.getTimestamp() < since || event.isStart()) {
                     continue;
                 }
-                batteryUsage += event.getBatteryUsage(since, Math.min(now, event.getTimestamp()));
+                batteryUsage.add(event.getBatteryUsage(since, Math.min(now, event.getTimestamp())));
                 if (now <= event.getTimestamp()) {
                     break;
                 }
             }
-            return Pair.create(batteryUsage, lastEvent.isStart() ? lastEvent.getBatteryUsage() : 0);
+            return Pair.create(batteryUsage.unmutate(), lastEvent.isStart()
+                    ? lastEvent.getBatteryUsage() : BATTERY_USAGE_NONE);
         }
 
         /**
          * @return The aggregated battery usage amongst all the event types we're tracking.
          */
-        Pair<Double, Double> getBatteryUsageSince(long since, long now) {
+        Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since,
+                long now) {
             LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
             for (int i = 0; i < mEvents.length; i++) {
                 result = add(result, mEvents[i]);
@@ -236,7 +246,7 @@
             UidStateEventWithBattery l = itl.next(), r = itr.next();
             LinkedList<UidStateEventWithBattery> dest = new LinkedList<>();
             boolean actl = false, actr = false, overlapping = false;
-            double batteryUsage = 0.0d;
+            final BatteryUsage batteryUsage = new BatteryUsage();
             long recentActTs = 0, overlappingDuration = 0;
             for (long lts = l.getTimestamp(), rts = r.getTimestamp();
                     lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
@@ -245,8 +255,8 @@
                 if (lts == rts) {
                     earliest = l;
                     // we'll deal with the double counting problem later.
-                    batteryUsage += actl ? l.getBatteryUsage() : 0.0d;
-                    batteryUsage += actr ? r.getBatteryUsage() : 0.0d;
+                    if (actl) batteryUsage.add(l.getBatteryUsage());
+                    if (actr) batteryUsage.add(r.getBatteryUsage());
                     overlappingDuration += overlapping && (actl || actr)
                             ? (lts - recentActTs) : 0;
                     actl = !actl;
@@ -255,13 +265,13 @@
                     rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
                 } else if (lts < rts) {
                     earliest = l;
-                    batteryUsage += actl ? l.getBatteryUsage() : 0.0d;
+                    if (actl) batteryUsage.add(l.getBatteryUsage());
                     overlappingDuration += overlapping && actl ? (lts - recentActTs) : 0;
                     actl = !actl;
                     lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
                 } else {
                     earliest = r;
-                    batteryUsage += actr ? r.getBatteryUsage() : 0.0d;
+                    if (actr) batteryUsage.add(r.getBatteryUsage());
                     overlappingDuration += overlapping && actr ? (rts - recentActTs) : 0;
                     actr = !actr;
                     rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
@@ -281,12 +291,12 @@
                         final long durationWithOverlapping = duration + overlappingDuration;
                         // Get the proportional batteryUsage.
                         if (durationWithOverlapping != 0) {
-                            batteryUsage *= duration * 1.0d / durationWithOverlapping;
+                            batteryUsage.scale(duration * 1.0d / durationWithOverlapping);
+                            event.update(lastEvent, new ImmutableBatteryUsage(batteryUsage));
                         } else {
-                            batteryUsage = 0.0d;
+                            event.update(lastEvent, BATTERY_USAGE_NONE);
                         }
-                        event.update(lastEvent, batteryUsage);
-                        batteryUsage = 0.0d;
+                        batteryUsage.setTo(BATTERY_USAGE_NONE);
                         overlappingDuration = 0;
                     }
                     dest.add(event);
@@ -322,14 +332,15 @@
          * the system boots if the {@link #mIsStart} is true, but will be the delta of the bg
          * battery usage since the start event if the {@link #mIsStart} is false.
          */
-        private double mBatteryUsage;
+        private @NonNull ImmutableBatteryUsage mBatteryUsage;
 
         /**
          * The peer event of this pair (a pair of start/stop events).
          */
         private @Nullable UidStateEventWithBattery mPeer;
 
-        UidStateEventWithBattery(boolean isStart, long now, double batteryUsage,
+        UidStateEventWithBattery(boolean isStart, long now,
+                @NonNull ImmutableBatteryUsage batteryUsage,
                 @Nullable UidStateEventWithBattery peer) {
             super(now);
             mIsStart = isStart;
@@ -355,15 +366,19 @@
             }
             if (mPeer != null) {
                 // Reduce the bg battery usage proportionally.
-                final double batteryUsage = mPeer.getBatteryUsage();
+                final ImmutableBatteryUsage batteryUsage = mPeer.getBatteryUsage();
                 mPeer.mBatteryUsage = mPeer.getBatteryUsage(timestamp, mPeer.mTimestamp);
                 // Update the battery data of the start event too.
-                mBatteryUsage += batteryUsage - mPeer.mBatteryUsage;
+                mBatteryUsage = mBatteryUsage.mutate()
+                        .add(batteryUsage)
+                        .subtract(mPeer.mBatteryUsage)
+                        .unmutate();
             }
             mTimestamp = timestamp;
         }
 
-        void update(@NonNull UidStateEventWithBattery peer, double batteryUsage) {
+        void update(@NonNull UidStateEventWithBattery peer,
+                @NonNull ImmutableBatteryUsage batteryUsage) {
             mPeer = peer;
             peer.mPeer = this;
             mBatteryUsage = batteryUsage;
@@ -373,18 +388,19 @@
             return mIsStart;
         }
 
-        double getBatteryUsage(long start, long end) {
+        @NonNull ImmutableBatteryUsage getBatteryUsage(long start, long end) {
             if (mIsStart || start >= mTimestamp || end <= start) {
-                return 0.0d;
+                return BATTERY_USAGE_NONE;
             }
             start = Math.max(start, mPeer.mTimestamp);
             end = Math.min(end, mTimestamp);
             final long totalDur = mTimestamp - mPeer.mTimestamp;
             final long inputDur = end - start;
-            return totalDur != 0 ? mBatteryUsage * (1.0d * inputDur) / totalDur : 0.0d;
+            return totalDur != 0 ? (totalDur == inputDur ? mBatteryUsage : mBatteryUsage.mutate()
+                    .scale((1.0d * inputDur) / totalDur).unmutate()) : BATTERY_USAGE_NONE;
         }
 
-        double getBatteryUsage() {
+        @NonNull ImmutableBatteryUsage getBatteryUsage() {
             return mBatteryUsage;
         }
 
@@ -404,14 +420,20 @@
             final UidStateEventWithBattery otherEvent = (UidStateEventWithBattery) other;
             return otherEvent.mIsStart == mIsStart
                     && otherEvent.mTimestamp == mTimestamp
-                    && Double.compare(otherEvent.mBatteryUsage, mBatteryUsage) == 0;
+                    && mBatteryUsage.equals(otherEvent.mBatteryUsage);
+        }
+
+        @Override
+        public String toString() {
+            return "UidStateEventWithBattery(" + mIsStart + ", " + mTimestamp
+                    + ", " + mBatteryUsage + ")";
         }
 
         @Override
         public int hashCode() {
             return (Boolean.hashCode(mIsStart) * 31
                     + Long.hashCode(mTimestamp)) * 31
-                    + Double.hashCode(mBatteryUsage);
+                    + mBatteryUsage.hashCode();
         }
     }
 
@@ -433,7 +455,8 @@
             super(injector, tracker,
                     KEY_BG_BATTERY_EXEMPTION_ENABLED, DEFAULT_BG_BATTERY_EXEMPTION_ENABLED,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+                    tracker.mContext.getResources()
+                    .getInteger(R.integer.config_bg_current_drain_window));
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index b8f5c50..6492662 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -28,23 +28,28 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_COUNT;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
 import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.util.TimeUtils.formatTime;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
-import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
 import static com.android.server.am.BaseAppStateTracker.ONE_MINUTE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager.RestrictionLevel;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.BatteryConsumer;
+import android.os.BatteryConsumer.Dimensions;
 import android.os.BatteryStatsInternal;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
@@ -58,9 +63,9 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.util.SparseDoubleArray;
 import android.util.TimeUtils;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -98,12 +103,7 @@
     static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG = 5 * ONE_MINUTE; // 5 mins
     static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG = 2_000L; // 2s
 
-    static final BatteryConsumer.Dimensions BATT_DIMEN_FG =
-            new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND);
-    static final BatteryConsumer.Dimensions BATT_DIMEN_BG =
-            new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_BACKGROUND);
-    static final BatteryConsumer.Dimensions BATT_DIMEN_FGS =
-            new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND_SERVICE);
+    static final ImmutableBatteryUsage BATTERY_USAGE_NONE = new ImmutableBatteryUsage();
 
     private final Runnable mBgBatteryUsageStatsPolling = this::updateBatteryUsageStatsAndCheck;
     private final Runnable mBgBatteryUsageStatsCheck = this::checkBatteryUsageStats;
@@ -132,29 +132,30 @@
      * the last battery stats reset prior to that (whoever is earlier).
      */
     @GuardedBy("mLock")
-    private final SparseDoubleArray mUidBatteryUsage = new SparseDoubleArray();
+    private final SparseArray<BatteryUsage> mUidBatteryUsage = new SparseArray<>();
 
     /**
      * The battery usage for each UID, in the rolling window of the past.
      */
     @GuardedBy("mLock")
-    private final SparseDoubleArray mUidBatteryUsageInWindow = new SparseDoubleArray();
+    private final SparseArray<ImmutableBatteryUsage> mUidBatteryUsageInWindow = new SparseArray<>();
 
     /**
      * The uid battery usage stats data from our last query, it consists of the data since
      * last battery stats reset.
      */
     @GuardedBy("mLock")
-    private final SparseDoubleArray mLastUidBatteryUsage = new SparseDoubleArray();
+    private final SparseArray<ImmutableBatteryUsage> mLastUidBatteryUsage = new SparseArray<>();
 
     // No lock is needed.
-    private final SparseDoubleArray mTmpUidBatteryUsage = new SparseDoubleArray();
+    private final SparseArray<BatteryUsage> mTmpUidBatteryUsage = new SparseArray<>();
 
     // No lock is needed.
-    private final SparseDoubleArray mTmpUidBatteryUsage2 = new SparseDoubleArray();
+    private final SparseArray<ImmutableBatteryUsage> mTmpUidBatteryUsage2 = new SparseArray<>();
 
     // No lock is needed.
-    private final SparseDoubleArray mTmpUidBatteryUsageInWindow = new SparseDoubleArray();
+    private final SparseArray<ImmutableBatteryUsage> mTmpUidBatteryUsageInWindow =
+            new SparseArray<>();
 
     // No lock is needed.
     private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>();
@@ -166,7 +167,7 @@
     private long mLastUidBatteryUsageStartTs;
 
     // For debug only.
-    final SparseDoubleArray mDebugUidPercentages = new SparseDoubleArray();
+    private final SparseArray<BatteryUsage> mDebugUidPercentages = new SparseArray<>();
 
     AppBatteryTracker(Context context, AppRestrictionController controller) {
         this(context, controller, null, null);
@@ -277,18 +278,24 @@
      * </p>
      */
     @Override
-    public double getUidBatteryUsage(int uid) {
+    @NonNull
+    public ImmutableBatteryUsage getUidBatteryUsage(int uid) {
         final long now = mInjector.currentTimeMillis();
         final boolean updated = updateBatteryUsageStatsIfNecessary(now, false);
         synchronized (mLock) {
             if (updated) {
                 // We just got fresh data, schedule a check right a way.
                 mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
-                if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) {
-                    mBgHandler.post(mBgBatteryUsageStatsCheck);
-                }
+                scheduleBgBatteryUsageStatsCheck();
             }
-            return mUidBatteryUsage.get(uid, 0.0d);
+            final BatteryUsage usage = mUidBatteryUsage.get(uid);
+            return usage != null ? new ImmutableBatteryUsage(usage) : BATTERY_USAGE_NONE;
+        }
+    }
+
+    private void scheduleBgBatteryUsageStatsCheck() {
+        if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) {
+            mBgHandler.post(mBgBatteryUsageStatsCheck);
         }
     }
 
@@ -309,27 +316,32 @@
         final long now = SystemClock.elapsedRealtime();
         final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
         try {
-            final SparseDoubleArray uidConsumers = mTmpUidBatteryUsageInWindow;
+            final SparseArray<ImmutableBatteryUsage> uidConsumers = mTmpUidBatteryUsageInWindow;
             synchronized (mLock) {
                 copyUidBatteryUsage(mUidBatteryUsageInWindow, uidConsumers);
             }
             final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
             for (int i = 0, size = uidConsumers.size(); i < size; i++) {
                 final int uid = uidConsumers.keyAt(i);
-                final double actualUsage = uidConsumers.valueAt(i);
-                final double exemptedUsage = mAppRestrictionController
+                final ImmutableBatteryUsage actualUsage = uidConsumers.valueAt(i);
+                final ImmutableBatteryUsage exemptedUsage = mAppRestrictionController
                         .getUidBatteryExemptedUsageSince(uid, since, now);
                 // It's possible the exemptedUsage could be larger than actualUsage,
                 // as the former one is an approximate value.
-                final double bgUsage = Math.max(0.0d, actualUsage - exemptedUsage);
-                final double percentage = bgPolicy.getPercentage(uid, bgUsage);
+                final BatteryUsage bgUsage = actualUsage.mutate()
+                        .subtract(exemptedUsage)
+                        .calcPercentage(uid, bgPolicy);
                 if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
                     Slog.i(TAG, String.format(
-                            "UID %d: %.3f mAh (or %4.2f%%) %.3f %.3f over the past %s",
-                            uid, bgUsage, percentage, exemptedUsage, actualUsage,
+                            "UID %d: %s (%s) | %s | %s over the past %s",
+                            uid,
+                            bgUsage.toString(),
+                            bgUsage.percentageToString(),
+                            exemptedUsage.toString(),
+                            actualUsage.toString(),
                             TimeUtils.formatDuration(bgPolicy.mBgCurrentDrainWindowMs)));
                 }
-                bgPolicy.handleUidBatteryUsage(uid, percentage);
+                bgPolicy.handleUidBatteryUsage(uid, bgUsage);
             }
             // For debugging only.
             for (int i = 0, size = mDebugUidPercentages.size(); i < size; i++) {
@@ -384,7 +396,7 @@
     private void updateBatteryUsageStatsOnce(long now) {
         final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
         final ArraySet<UserHandle> userIds = mTmpUserIds;
-        final SparseDoubleArray buf = mTmpUidBatteryUsage;
+        final SparseArray<BatteryUsage> buf = mTmpUidBatteryUsage;
         final BatteryStatsInternal batteryStatsInternal = mInjector.getBatteryStatsInternal();
         final long windowSize = bgPolicy.mBgCurrentDrainWindowMs;
 
@@ -453,26 +465,26 @@
             for (int i = 0, size = buf.size(); i < size; i++) {
                 final int uid = buf.keyAt(i);
                 final int index = mUidBatteryUsage.indexOfKey(uid);
-                final double lastUsage = mLastUidBatteryUsage.get(uid, 0.0d);
-                final double curUsage = buf.valueAt(i);
-                final double before;
+                final BatteryUsage lastUsage = mLastUidBatteryUsage.get(uid, BATTERY_USAGE_NONE);
+                final BatteryUsage curUsage = buf.valueAt(i);
+                final BatteryUsage before;
                 if (index >= 0) {
                     before = mUidBatteryUsage.valueAt(index);
-                    mUidBatteryUsage.setValueAt(index, before - lastUsage + curUsage);
+                    before.subtract(lastUsage).add(curUsage);
                 } else {
-                    before = 0.0d;
+                    before = BATTERY_USAGE_NONE;
                     mUidBatteryUsage.put(uid, curUsage);
                 }
                 if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
-                    final double actualDelta = curUsage - lastUsage;
+                    final BatteryUsage actualDelta = new BatteryUsage(curUsage).subtract(lastUsage);
                     String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
-                            + ", after=" + mUidBatteryUsage.get(uid, 0.0d)
+                            + ", after=" + mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE)
                             + ", delta=" + actualDelta
                             + ", last=" + lastUsage
                             + ", curStart=" + curStart
                             + ", lastLastStart=" + lastUidBatteryUsageStartTs
                             + ", thisLastStart=" + mLastUidBatteryUsageStartTs;
-                    if (actualDelta < 0.0d) {
+                    if (!actualDelta.isValid()) {
                         // Something is wrong, the battery usage shouldn't be negative.
                         Slog.e(TAG, msg);
                     } else {
@@ -508,8 +520,8 @@
         }
     }
 
-    private static BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration,
-            SparseDoubleArray buf, BatteryUsageStatsQuery.Builder builder,
+    private BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration,
+            SparseArray<BatteryUsage> buf, BatteryUsageStatsQuery.Builder builder,
             ArraySet<UserHandle> userIds, BatteryStatsInternal batteryStatsInternal) {
         for (int i = 0, size = userIds.size(); i < size; i++) {
             builder.addUser(userIds.valueAt(i));
@@ -527,16 +539,19 @@
             final long end = stats.getStatsEndTimestamp();
             final double scale = expectedDuration > 0
                     ? (expectedDuration * 1.0d) / (end - start) : 1.0d;
+            final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
             for (UidBatteryConsumer uidConsumer : uidConsumers) {
                 // TODO: b/200326767 - as we are not supporting per proc state attribution yet,
                 // we couldn't distinguish between a real FGS vs. a bound FGS proc state.
                 final int uid = uidConsumer.getUid();
-                final double bgUsage = getBgUsage(uidConsumer) * scale;
+                final BatteryUsage bgUsage = new BatteryUsage(uidConsumer, bgPolicy)
+                        .scale(scale);
                 int index = buf.indexOfKey(uid);
                 if (index < 0) {
                     buf.put(uid, bgUsage);
                 } else {
-                    buf.setValueAt(index, buf.valueAt(index) + bgUsage);
+                    final BatteryUsage before = buf.valueAt(index);
+                    before.add(bgUsage);
                 }
                 if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
                     Slog.i(TAG, "updateBatteryUsageStatsOnceInternal uid=" + uid
@@ -549,32 +564,19 @@
         return stats;
     }
 
-    private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest) {
+    private static void copyUidBatteryUsage(SparseArray<? extends BatteryUsage> source,
+            SparseArray<ImmutableBatteryUsage> dest) {
         dest.clear();
         for (int i = source.size() - 1; i >= 0; i--) {
-            dest.put(source.keyAt(i), source.valueAt(i));
+            dest.put(source.keyAt(i), new ImmutableBatteryUsage(source.valueAt(i)));
         }
     }
 
-    private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest,
-            double scale) {
+    private static void copyUidBatteryUsage(SparseArray<? extends BatteryUsage> source,
+            SparseArray<ImmutableBatteryUsage> dest, double scale) {
         dest.clear();
         for (int i = source.size() - 1; i >= 0; i--) {
-            dest.put(source.keyAt(i), source.valueAt(i) * scale);
-        }
-    }
-
-    private static double getBgUsage(final UidBatteryConsumer uidConsumer) {
-        return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG)
-                + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS);
-    }
-
-    private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
-            final BatteryConsumer.Dimensions dimens) {
-        try {
-            return uidConsumer.getConsumedPower(dimens);
-        } catch (IllegalArgumentException e) {
-            return 0.0d;
+            dest.put(source.keyAt(i), new ImmutableBatteryUsage(source.valueAt(i), scale));
         }
     }
 
@@ -602,6 +604,19 @@
         }
     }
 
+    void setDebugUidPercentage(int[] uids, double[][] percentages) {
+        mDebugUidPercentages.clear();
+        for (int i = 0; i < uids.length; i++) {
+            mDebugUidPercentages.put(uids[i], new BatteryUsage().setPercentage(percentages[i]));
+        }
+        scheduleBgBatteryUsageStatsCheck();
+    }
+
+    void clearDebugUidPercentage() {
+        mDebugUidPercentages.clear();
+        scheduleBgBatteryUsageStatsCheck();
+    }
+
     @VisibleForTesting
     void reset() {
         synchronized (mLock) {
@@ -620,7 +635,7 @@
         pw.println("APP BATTERY STATE TRACKER:");
         updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
         synchronized (mLock) {
-            final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+            final SparseArray<ImmutableBatteryUsage> uidConsumers = mUidBatteryUsageInWindow;
             pw.print("  " + prefix);
             pw.print("  Last battery usage start=");
             TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
@@ -638,26 +653,285 @@
             } else {
                 for (int i = 0, size = uidConsumers.size(); i < size; i++) {
                     final int uid = uidConsumers.keyAt(i);
-                    final double bgUsage = uidConsumers.valueAt(i);
-                    final double exemptedUsage = mAppRestrictionController
-                            .getUidBatteryExemptedUsageSince(uid, since, now);
-                    final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage);
-                    pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | "
-                            + "%.3f mAh (%4.2f%%) | %.3f mAh\n",
+                    final BatteryUsage bgUsage = uidConsumers.valueAt(i)
+                            .calcPercentage(uid, bgPolicy);
+                    final BatteryUsage exemptedUsage = mAppRestrictionController
+                            .getUidBatteryExemptedUsageSince(uid, since, now)
+                            .calcPercentage(uid, bgPolicy);
+                    final BatteryUsage reportedUsage = new BatteryUsage(bgUsage)
+                            .subtract(exemptedUsage)
+                            .calcPercentage(uid, bgPolicy);
+                    pw.format("%s%s: [%s] %s (%s) | %s (%s) | %s (%s) | %s\n",
                             newPrefix, UserHandle.formatUid(uid),
                             PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
-                            bgUsage , bgPolicy.getPercentage(uid, bgUsage),
-                            exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage),
-                            reportedUsage, bgPolicy.getPercentage(-1, reportedUsage),
-                            mUidBatteryUsage.get(uid, 0.0d));
+                            bgUsage.toString(),
+                            bgUsage.percentageToString(),
+                            exemptedUsage.toString(),
+                            exemptedUsage.percentageToString(),
+                            reportedUsage.toString(),
+                            reportedUsage.percentageToString(),
+                            mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE).toString());
                 }
             }
         }
         super.dump(pw, prefix);
     }
 
+    static class BatteryUsage {
+        static final int BATTERY_USAGE_INDEX_UNSPECIFIED = PROCESS_STATE_UNSPECIFIED;
+        static final int BATTERY_USAGE_INDEX_FOREGROUND = PROCESS_STATE_FOREGROUND;
+        static final int BATTERY_USAGE_INDEX_BACKGROUND = PROCESS_STATE_BACKGROUND;
+        static final int BATTERY_USAGE_INDEX_FOREGROUND_SERVICE = PROCESS_STATE_FOREGROUND_SERVICE;
+        static final int BATTERY_USAGE_COUNT = PROCESS_STATE_COUNT;
+
+        static final Dimensions[] BATT_DIMENS = new Dimensions[] {
+                new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+                        PROCESS_STATE_UNSPECIFIED),
+                new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+                        PROCESS_STATE_FOREGROUND),
+                new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+                        PROCESS_STATE_BACKGROUND),
+                new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+                        PROCESS_STATE_FOREGROUND_SERVICE),
+        };
+
+        @NonNull double[] mUsage;
+        @Nullable double[] mPercentage;
+
+        BatteryUsage() {
+            this(0.0d, 0.0d, 0.0d, 0.0d);
+        }
+
+        BatteryUsage(double unspecifiedUsage, double fgUsage, double bgUsage, double fgsUsage) {
+            mUsage = new double[] {unspecifiedUsage, fgUsage, bgUsage, fgsUsage};
+        }
+
+        BatteryUsage(@NonNull double[] usage) {
+            mUsage = usage;
+        }
+
+        BatteryUsage(@NonNull BatteryUsage other, double scale) {
+            this(other);
+            scaleInternal(scale);
+        }
+
+        BatteryUsage(@NonNull BatteryUsage other) {
+            mUsage = new double[other.mUsage.length];
+            setToInternal(other);
+        }
+
+        BatteryUsage(@NonNull UidBatteryConsumer consumer, @NonNull AppBatteryPolicy policy) {
+            final Dimensions[] dims = policy.mBatteryDimensions;
+            mUsage = new double[] {
+                    getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_UNSPECIFIED]),
+                    getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_FOREGROUND]),
+                    getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_BACKGROUND]),
+                    getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE])
+            };
+        }
+
+        BatteryUsage setTo(@NonNull BatteryUsage other) {
+            return setToInternal(other);
+        }
+
+        private BatteryUsage setToInternal(@NonNull BatteryUsage other) {
+            for (int i = 0; i < other.mUsage.length; i++) {
+                mUsage[i] = other.mUsage[i];
+            }
+            return this;
+        }
+
+        BatteryUsage add(@NonNull BatteryUsage other) {
+            for (int i = 0; i < other.mUsage.length; i++) {
+                mUsage[i] += other.mUsage[i];
+            }
+            return this;
+        }
+
+        BatteryUsage subtract(@NonNull BatteryUsage other) {
+            for (int i = 0; i < other.mUsage.length; i++) {
+                mUsage[i] = Math.max(0.0d, mUsage[i] - other.mUsage[i]);
+            }
+            return this;
+        }
+
+        BatteryUsage scale(double scale) {
+            return scaleInternal(scale);
+        }
+
+        private BatteryUsage scaleInternal(double scale) {
+            for (int i = 0; i < mUsage.length; i++) {
+                mUsage[i] *= scale;
+            }
+            return this;
+        }
+
+        ImmutableBatteryUsage unmutate() {
+            return new ImmutableBatteryUsage(this);
+        }
+
+        BatteryUsage calcPercentage(int uid, @NonNull AppBatteryPolicy policy) {
+            if (mPercentage == null || mPercentage.length != mUsage.length) {
+                mPercentage = new double[mUsage.length];
+            }
+            policy.calcPercentage(uid, mUsage, mPercentage);
+            return this;
+        }
+
+        BatteryUsage setPercentage(@NonNull double[] percentage) {
+            mPercentage = percentage;
+            return this;
+        }
+
+        double[] getPercentage() {
+            return mPercentage;
+        }
+
+        String percentageToString() {
+            return formatBatteryUsagePercentage(mPercentage);
+        }
+
+        @Override
+        public String toString() {
+            return formatBatteryUsage(mUsage);
+        }
+
+        boolean isValid() {
+            for (int i = 0; i < mUsage.length; i++) {
+                if (mUsage[i] < 0.0d) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        boolean isEmpty() {
+            for (int i = 0; i < mUsage.length; i++) {
+                if (mUsage[i] > 0.0d) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null) {
+                return false;
+            }
+            final BatteryUsage otherUsage = (BatteryUsage) other;
+            for (int i = 0; i < mUsage.length; i++) {
+                if (Double.compare(mUsage[i], otherUsage.mUsage[i]) != 0) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int hashCode = 0;
+            for (int i = 0; i < mUsage.length; i++) {
+                hashCode = Double.hashCode(mUsage[i]) + hashCode * 31;
+            }
+            return hashCode;
+        }
+
+        private static String formatBatteryUsage(double[] usage) {
+            return String.format("%.3f %.3f %.3f %.3f mAh",
+                    usage[BATTERY_USAGE_INDEX_UNSPECIFIED],
+                    usage[BATTERY_USAGE_INDEX_FOREGROUND],
+                    usage[BATTERY_USAGE_INDEX_BACKGROUND],
+                    usage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]);
+        }
+
+        static String formatBatteryUsagePercentage(double[] percentage) {
+            return String.format("%4.2f%% %4.2f%% %4.2f%% %4.2f%%",
+                    percentage[BATTERY_USAGE_INDEX_UNSPECIFIED],
+                    percentage[BATTERY_USAGE_INDEX_FOREGROUND],
+                    percentage[BATTERY_USAGE_INDEX_BACKGROUND],
+                    percentage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]);
+        }
+
+        private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
+                final Dimensions dimens) {
+            try {
+                return uidConsumer.getConsumedPower(dimens);
+            } catch (IllegalArgumentException e) {
+                return 0.0d;
+            }
+        }
+    }
+
+    static final class ImmutableBatteryUsage extends BatteryUsage {
+        ImmutableBatteryUsage() {
+            super();
+        }
+
+        ImmutableBatteryUsage(double unspecifiedUsage, double fgUsage, double bgUsage,
+                double fgsUsage) {
+            super(unspecifiedUsage, fgUsage, bgUsage, fgsUsage);
+        }
+
+        ImmutableBatteryUsage(@NonNull double[] usage) {
+            super(usage);
+        }
+
+        ImmutableBatteryUsage(@NonNull BatteryUsage other, double scale) {
+            super(other, scale);
+        }
+
+        ImmutableBatteryUsage(@NonNull BatteryUsage other) {
+            super(other);
+        }
+
+        ImmutableBatteryUsage(@NonNull UidBatteryConsumer consumer,
+                @NonNull AppBatteryPolicy policy) {
+            super(consumer, policy);
+        }
+
+        @Override
+        BatteryUsage setTo(@NonNull BatteryUsage other) {
+            throw new RuntimeException("Readonly");
+        }
+
+        @Override
+        BatteryUsage add(@NonNull BatteryUsage other) {
+            throw new RuntimeException("Readonly");
+        }
+
+        @Override
+        BatteryUsage subtract(@NonNull BatteryUsage other) {
+            throw new RuntimeException("Readonly");
+        }
+
+        @Override
+        BatteryUsage scale(double scale) {
+            throw new RuntimeException("Readonly");
+        }
+
+        @Override
+        BatteryUsage setPercentage(@NonNull double[] percentage) {
+            throw new RuntimeException("Readonly");
+        }
+
+        BatteryUsage mutate() {
+            return new BatteryUsage(this);
+        }
+    }
+
     static final class AppBatteryPolicy extends BaseAppStatePolicy<AppBatteryTracker> {
         /**
+         * The type of battery usage we could choose to apply the policy on.
+         *
+         * Must be in sync with android.os.BatteryConsumer.PROCESS_STATE_*.
+         */
+        static final int BATTERY_USAGE_TYPE_UNSPECIFIED = 1;
+        static final int BATTERY_USAGE_TYPE_FOREGROUND = 1 << 1;
+        static final int BATTERY_USAGE_TYPE_BACKGROUND = 1 << 2;
+        static final int BATTERY_USAGE_TYPE_FOREGROUND_SERVICE = 1 << 3;
+
+        /**
          * Whether or not we should enable the monitoring on background current drains.
          */
         static final String KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED =
@@ -729,58 +1003,90 @@
                 + "current_drain_event_duration_based_threshold_enabled";
 
         /**
-         * Default value to {@link #mTrackerEnabled}.
+         * The types of battery drain we're checking on each app; if the sum of the battery drain
+         * exceeds the threshold, it'll be moved to restricted standby bucket; the type here
+         * must be one of, or combination of {@link #BATTERY_USAGE_TYPE_BACKGROUND} and
+         * {@link #BATTERY_USAGE_TYPE_FOREGROUND_SERVICE}.
          */
-        static final boolean DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED = true;
+        static final String KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_types_to_restricted_bucket";
+
+        /**
+         * The types of battery drain we're checking on each app; if the sum of the battery drain
+         * exceeds the threshold, it'll be moved to background restricted level; the type here
+         * must be one of, or combination of {@link #BATTERY_USAGE_TYPE_BACKGROUND} and
+         * {@link #BATTERY_USAGE_TYPE_FOREGROUND_SERVICE}.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_types_to_bg_restricted";
+
+        /**
+         * The power usage components we're monitoring.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_power_components";
 
         /**
          * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
          * the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
          */
-        static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD =
-                isLowRamDeviceStatic() ? 4.0f : 2.0f;
+        final float mDefaultBgCurrentDrainRestrictedBucket;
 
         /**
          * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
          * the {@link #mBgCurrentDrainBgRestrictedThreshold}.
          */
-        static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD =
-                isLowRamDeviceStatic() ? 8.0f : 4.0f;
+        final float mDefaultBgCurrentDrainBgRestrictedThreshold;
 
         /**
          * Default value to {@link #mBgCurrentDrainWindowMs}.
          */
-        static final long DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS = ONE_DAY;
+        final long mDefaultBgCurrentDrainWindowMs;
 
         /**
          * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
          * the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
          */
-        static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD =
-                isLowRamDeviceStatic() ? 60.0f : 30.0f;
+        final float mDefaultBgCurrentDrainRestrictedBucketHighThreshold;
 
         /**
          * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
          * the {@link #mBgCurrentDrainBgRestrictedThreshold}.
          */
-        static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD =
-                isLowRamDeviceStatic() ? 60.0f : 30.0f;
+        final float mDefaultBgCurrentDrainBgRestrictedHighThreshold;
 
         /**
          * Default value to {@link #mBgCurrentDrainMediaPlaybackMinDuration}.
          */
-        static final long DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION = 30 * ONE_MINUTE;
+        final long mDefaultBgCurrentDrainMediaPlaybackMinDuration;
 
         /**
          * Default value to {@link #mBgCurrentDrainLocationMinDuration}.
          */
-        static final long DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION = 30 * ONE_MINUTE;
+        final long mDefaultBgCurrentDrainLocationMinDuration;
 
         /**
          * Default value to {@link #mBgCurrentDrainEventDurationBasedThresholdEnabled}.
          */
-        static final boolean DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED =
-                false;
+        final boolean mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled;
+
+        /**
+         * Default value to {@link #mBgCurrentDrainRestrictedBucketTypes}.
+         */
+        final int mDefaultCurrentDrainTypesToRestrictedBucket;
+
+        /**
+         * Default value to {@link #mBgCurrentDrainBgRestrictedTypes}.
+         */
+        final int mDefaultBgCurrentDrainTypesToBgRestricted;
+
+        /**
+         * Default value to {@link #mBgCurrentDrainPowerComponents}.
+         **/
+        @BatteryConsumer.PowerComponent
+        static final int DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS = POWER_COMPONENT_ANY;
+
+        final int mDefaultBgCurrentDrainPowerComponent;
 
         /**
          * The index to {@link #mBgCurrentDrainRestrictedBucketThreshold}
@@ -793,36 +1099,28 @@
          * @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET.
          * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET.
          */
-        volatile float[] mBgCurrentDrainRestrictedBucketThreshold = {
-                DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD,
-                DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD,
-        };
+        volatile float[] mBgCurrentDrainRestrictedBucketThreshold = new float[2];
 
         /**
          * @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED.
          * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED.
          */
-        volatile float[] mBgCurrentDrainBgRestrictedThreshold = {
-                DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD,
-                DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD,
-        };
+        volatile float[] mBgCurrentDrainBgRestrictedThreshold = new float[2];
 
         /**
          * @see #KEY_BG_CURRENT_DRAIN_WINDOW.
          */
-        volatile long mBgCurrentDrainWindowMs = DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS;
+        volatile long mBgCurrentDrainWindowMs;
 
         /**
          * @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION.
          */
-        volatile long mBgCurrentDrainMediaPlaybackMinDuration =
-                DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION;
+        volatile long mBgCurrentDrainMediaPlaybackMinDuration;
 
         /**
          * @see #KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION.
          */
-        volatile long mBgCurrentDrainLocationMinDuration =
-                DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION;
+        volatile long mBgCurrentDrainLocationMinDuration;
 
         /**
          * @see #KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED.
@@ -830,6 +1128,24 @@
         volatile boolean mBgCurrentDrainEventDurationBasedThresholdEnabled;
 
         /**
+         * @see #KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET.
+         */
+        volatile int mBgCurrentDrainRestrictedBucketTypes;
+
+        /**
+         * @see #KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED.
+         */
+        volatile int mBgCurrentDrainBgRestrictedTypes;
+
+        /**
+         * @see #KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS.
+         */
+        @BatteryConsumer.PowerComponent
+        volatile int mBgCurrentDrainPowerComponents;
+
+        volatile Dimensions[] mBatteryDimensions;
+
+        /**
          * The capacity of the battery when fully charged in mAh.
          */
         private int mBatteryFullChargeMah;
@@ -851,8 +1167,62 @@
 
         AppBatteryPolicy(@NonNull Injector injector, @NonNull AppBatteryTracker tracker) {
             super(injector, tracker, KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
-                    DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+                    tracker.mContext.getResources()
+                    .getBoolean(R.bool.config_bg_current_drain_monitor_enabled));
             mLock = tracker.mLock;
+            final Resources resources = tracker.mContext.getResources();
+            float[] val = getFloatArray(resources.obtainTypedArray(
+                    R.array.config_bg_current_drain_threshold_to_restricted_bucket));
+            mDefaultBgCurrentDrainRestrictedBucket =
+                    isLowRamDeviceStatic() ? val[1] : val[0];
+            val = getFloatArray(resources.obtainTypedArray(
+                    R.array.config_bg_current_drain_threshold_to_bg_restricted));
+            mDefaultBgCurrentDrainBgRestrictedThreshold =
+                    isLowRamDeviceStatic() ? val[1] : val[0];
+            mDefaultBgCurrentDrainWindowMs = resources.getInteger(
+                    R.integer.config_bg_current_drain_window);
+            val = getFloatArray(resources.obtainTypedArray(
+                    R.array.config_bg_current_drain_high_threshold_to_restricted_bucket));
+            mDefaultBgCurrentDrainRestrictedBucketHighThreshold =
+                    isLowRamDeviceStatic() ? val[1] : val[0];
+            val = getFloatArray(resources.obtainTypedArray(
+                    R.array.config_bg_current_drain_high_threshold_to_bg_restricted));
+            mDefaultBgCurrentDrainBgRestrictedHighThreshold =
+                    isLowRamDeviceStatic() ? val[1] : val[0];
+            mDefaultBgCurrentDrainMediaPlaybackMinDuration = resources.getInteger(
+                    R.integer.config_bg_current_drain_media_playback_min_duration);
+            mDefaultBgCurrentDrainLocationMinDuration = resources.getInteger(
+                    R.integer.config_bg_current_drain_location_min_duration);
+            mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled = resources.getBoolean(
+                    R.bool.config_bg_current_drain_event_duration_based_threshold_enabled);
+            mDefaultCurrentDrainTypesToRestrictedBucket = resources.getInteger(
+                    R.integer.config_bg_current_drain_types_to_restricted_bucket);
+            mDefaultBgCurrentDrainTypesToBgRestricted = resources.getInteger(
+                    R.integer.config_bg_current_drain_types_to_bg_restricted);
+            mDefaultBgCurrentDrainPowerComponent = resources.getInteger(
+                    R.integer.config_bg_current_drain_power_components);
+            mBgCurrentDrainRestrictedBucketThreshold[0] =
+                    mDefaultBgCurrentDrainRestrictedBucket;
+            mBgCurrentDrainRestrictedBucketThreshold[1] =
+                    mDefaultBgCurrentDrainRestrictedBucketHighThreshold;
+            mBgCurrentDrainBgRestrictedThreshold[0] =
+                    mDefaultBgCurrentDrainBgRestrictedThreshold;
+            mBgCurrentDrainBgRestrictedThreshold[1] =
+                    mDefaultBgCurrentDrainBgRestrictedHighThreshold;
+            mBgCurrentDrainWindowMs = mDefaultBgCurrentDrainWindowMs;
+            mBgCurrentDrainMediaPlaybackMinDuration =
+                    mDefaultBgCurrentDrainMediaPlaybackMinDuration;
+            mBgCurrentDrainLocationMinDuration = mDefaultBgCurrentDrainLocationMinDuration;
+        }
+
+        static float[] getFloatArray(TypedArray array) {
+            int length = array.length();
+            float[] floatArray = new float[length];
+            for (int i = 0; i < length; i++) {
+                floatArray[i] = array.getFloat(i, Float.NaN);
+            }
+            array.recycle();
+            return floatArray;
         }
 
         @Override
@@ -862,6 +1232,9 @@
                 case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED:
                 case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET:
                 case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED:
+                case KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET:
+                case KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED:
+                case KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS:
                     updateCurrentDrainThreshold();
                     break;
                 case KEY_BG_CURRENT_DRAIN_WINDOW:
@@ -899,48 +1272,67 @@
             mBgCurrentDrainRestrictedBucketThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
                     DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
-                    DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD);
+                    mDefaultBgCurrentDrainRestrictedBucket);
             mBgCurrentDrainRestrictedBucketThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
                     DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
-                    DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD);
+                    mDefaultBgCurrentDrainRestrictedBucketHighThreshold);
             mBgCurrentDrainBgRestrictedThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
                     DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
-                    DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+                    mDefaultBgCurrentDrainBgRestrictedThreshold);
             mBgCurrentDrainBgRestrictedThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
                     DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
-                    DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+                    mDefaultBgCurrentDrainBgRestrictedHighThreshold);
+            mBgCurrentDrainRestrictedBucketTypes =
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET,
+                    mDefaultCurrentDrainTypesToRestrictedBucket);
+            mBgCurrentDrainBgRestrictedTypes =
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED,
+                    mDefaultBgCurrentDrainTypesToBgRestricted);
+            mBgCurrentDrainPowerComponents =
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS,
+                    mDefaultBgCurrentDrainPowerComponent);
+            if (mBgCurrentDrainPowerComponents == DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS) {
+                mBatteryDimensions = BatteryUsage.BATT_DIMENS;
+            } else {
+                mBatteryDimensions = new Dimensions[BatteryUsage.BATTERY_USAGE_COUNT];
+                for (int i = 0; i < BatteryUsage.BATTERY_USAGE_COUNT; i++) {
+                    mBatteryDimensions[i] = new Dimensions(mBgCurrentDrainPowerComponents, i);
+                }
+            }
         }
 
         private void updateCurrentDrainWindow() {
             mBgCurrentDrainWindowMs = DeviceConfig.getLong(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_BG_CURRENT_DRAIN_WINDOW,
-                    mBgCurrentDrainWindowMs != DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS
-                    ? mBgCurrentDrainWindowMs : DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+                    mDefaultBgCurrentDrainWindowMs);
         }
 
         private void updateCurrentDrainMediaPlaybackMinDuration() {
             mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
-                    DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+                    mDefaultBgCurrentDrainMediaPlaybackMinDuration);
         }
 
         private void updateCurrentDrainLocationMinDuration() {
             mBgCurrentDrainLocationMinDuration = DeviceConfig.getLong(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
-                    DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+                    mDefaultBgCurrentDrainLocationMinDuration);
         }
 
         private void updateCurrentDrainEventDurationBasedThresholdEnabled() {
             mBgCurrentDrainEventDurationBasedThresholdEnabled = DeviceConfig.getBoolean(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
-                    DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+                    mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled);
         }
 
         @Override
@@ -970,18 +1362,58 @@
             }
         }
 
-        double getBgUsage(final UidBatteryConsumer uidConsumer) {
-            return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG)
-                    + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS);
+        double[] calcPercentage(final int uid, final double[] usage, double[] percentage) {
+            final BatteryUsage debugUsage = uid > 0 ? mTracker.mDebugUidPercentages.get(uid) : null;
+            final double[] forced = debugUsage != null ? debugUsage.getPercentage() : null;
+            for (int i = 0; i < usage.length; i++) {
+                percentage[i] = forced != null ? forced[i] : usage[i] / mBatteryFullChargeMah * 100;
+            }
+            return percentage;
         }
 
-        double getPercentage(final int uid, final double usage) {
-            final double actualPercentage = usage / mBatteryFullChargeMah * 100;
-            return DEBUG_BACKGROUND_BATTERY_TRACKER
-                    ? mTracker.mDebugUidPercentages.get(uid, actualPercentage) : actualPercentage;
+        private double sumPercentageOfTypes(double[] percentage, int types) {
+            double result = 0.0d;
+            for (int type = Integer.highestOneBit(types); type != 0;
+                    type = Integer.highestOneBit(types)) {
+                final int index = Integer.numberOfTrailingZeros(type);
+                result += percentage[index];
+                types &= ~type;
+            }
+            return result;
         }
 
-        void handleUidBatteryUsage(final int uid, final double percentage) {
+        private static String batteryUsageTypesToString(int types) {
+            final StringBuilder sb = new StringBuilder("[");
+            boolean needDelimiter = false;
+            for (int type = Integer.highestOneBit(types); type != 0;
+                    type = Integer.highestOneBit(types)) {
+                if (needDelimiter) {
+                    sb.append('|');
+                }
+                needDelimiter = true;
+                switch (type) {
+                    case BATTERY_USAGE_TYPE_UNSPECIFIED:
+                        sb.append("UNSPECIFIED");
+                        break;
+                    case BATTERY_USAGE_TYPE_FOREGROUND:
+                        sb.append("FOREGROUND");
+                        break;
+                    case BATTERY_USAGE_TYPE_BACKGROUND:
+                        sb.append("BACKGROUND");
+                        break;
+                    case BATTERY_USAGE_TYPE_FOREGROUND_SERVICE:
+                        sb.append("FOREGROUND_SERVICE");
+                        break;
+                    default:
+                        return "[UNKNOWN(" + Integer.toHexString(types) + ")]";
+                }
+                types &= ~type;
+            }
+            sb.append("]");
+            return sb.toString();
+        }
+
+        void handleUidBatteryUsage(final int uid, final BatteryUsage usage) {
             final @ReasonCode int reason = shouldExemptUid(uid);
             if (reason != REASON_DENIED) {
                 if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
@@ -992,6 +1424,10 @@
             }
             boolean notifyController = false;
             boolean excessive = false;
+            final double rbPercentage = sumPercentageOfTypes(usage.getPercentage(),
+                    mBgCurrentDrainRestrictedBucketTypes);
+            final double brPercentage = sumPercentageOfTypes(usage.getPercentage(),
+                    mBgCurrentDrainBgRestrictedTypes);
             synchronized (mLock) {
                 final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(uid);
                 if (curLevel >= RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
@@ -1003,7 +1439,7 @@
                         mBgCurrentDrainWindowMs);
                 final int index = mHighBgBatteryPackages.indexOfKey(uid);
                 if (index < 0) {
-                    if (percentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
+                    if (rbPercentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
                         // New findings to us, track it and let the controller know.
                         final long[] ts = new long[TIME_STAMP_INDEX_LAST];
                         ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
@@ -1012,13 +1448,13 @@
                     }
                 } else {
                     final long[] ts = mHighBgBatteryPackages.valueAt(index);
-                    if (percentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
+                    if (rbPercentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
                         // it's actually back to normal, but we don't untrack it until
                         // explicit user interactions.
                         notifyController = true;
                     } else {
                         excessive = true;
-                        if (percentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) {
+                        if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) {
                             // If we're in the restricted standby bucket but still seeing high
                             // current drains, tell the controller again.
                             if (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
@@ -1037,7 +1473,7 @@
             if (excessive) {
                 if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
                     Slog.i(TAG, "Excessive background current drain " + uid
-                            + String.format(" %.2f%%", percentage) + " over "
+                            + usage + " (" + usage.percentageToString() + " ) over "
                             + TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
                 }
                 if (notifyController) {
@@ -1048,7 +1484,7 @@
             } else {
                 if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
                     Slog.i(TAG, "Background current drain backs to normal " + uid
-                            + String.format(" %.2f%%", percentage) + " over "
+                            + usage + " (" + usage.percentageToString() + " ) over "
                             + TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
                 }
                 // For now, we're not lifting the restrictions if the bg current drain backs to
@@ -1120,15 +1556,6 @@
             }
         }
 
-        private double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
-                final BatteryConsumer.Dimensions dimens) {
-            try {
-                return uidConsumer.getConsumedPower(dimens);
-            } catch (IllegalArgumentException e) {
-                return 0.0d;
-            }
-        }
-
         @VisibleForTesting
         void reset() {
             mHighBgBatteryPackages.clear();
@@ -1179,6 +1606,18 @@
                 pw.print(KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
                 pw.print('=');
                 pw.println(mBgCurrentDrainEventDurationBasedThresholdEnabled);
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET);
+                pw.print('=');
+                pw.println(batteryUsageTypesToString(mBgCurrentDrainRestrictedBucketTypes));
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED);
+                pw.print('=');
+                pw.println(batteryUsageTypesToString(mBgCurrentDrainBgRestrictedTypes));
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS);
+                pw.print('=');
+                pw.println(mBgCurrentDrainPowerComponents);
 
                 pw.print(prefix);
                 pw.println("Excessive current drain detected:");
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 8cff13e..a3aa129 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -142,6 +142,7 @@
 import com.android.server.AppStateTracker;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
+import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage;
 import com.android.server.apphibernation.AppHibernationManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.usage.AppStandbyInternal;
@@ -1076,7 +1077,7 @@
      * @return The to-be-exempted battery usage of the given UID in the given duration; it could
      *         be considered as "exempted" due to various use cases, i.e. media playback.
      */
-    double getUidBatteryExemptedUsageSince(int uid, long since, long now) {
+    ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now) {
         return mInjector.getAppBatteryExemptionTracker()
                 .getUidBatteryExemptedUsageSince(uid, since, now);
     }
@@ -1084,7 +1085,7 @@
     /**
      * @return The total battery usage of the given UID since the system boots.
      */
-    double getUidBatteryUsage(int uid) {
+    @NonNull ImmutableBatteryUsage getUidBatteryUsage(int uid) {
         return mInjector.getUidBatteryUsageProvider().getUidBatteryUsage(uid);
     }
 
@@ -1092,7 +1093,7 @@
         /**
          * @return The total battery usage of the given UID since the system boots.
          */
-        double getUidBatteryUsage(int uid);
+        @NonNull ImmutableBatteryUsage getUidBatteryUsage(int uid);
     }
 
     void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index bdfd02e..6c9187a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2491,6 +2491,7 @@
                 mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
             } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
                     && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
+                    && !state.isRunningRemoteAnimation()
                     // Because these can fire independent of oom_adj/procstate changes, we need
                     // to throttle the actual dispatch of these requests in addition to the
                     // processing of the requests. As a result, there is throttling both here
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 1ad0bce..c4163e6 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1723,6 +1723,12 @@
             return Zygote.MEMORY_TAG_LEVEL_TBI;
         }
 
+        String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
+        if ("sync".equals(defaultLevel)) {
+            return Zygote.MEMORY_TAG_LEVEL_SYNC;
+        } else if ("async".equals(defaultLevel)) {
+            return Zygote.MEMORY_TAG_LEVEL_ASYNC;
+        }
         return Zygote.MEMORY_TAG_LEVEL_NONE;
     }
 
@@ -1918,7 +1924,7 @@
             if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL) != 0) {
                 runtimeFlags |= Zygote.PROFILE_FROM_SHELL;
             }
-            if ((app.info.privateFlagsExt & ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE) != 0) {
+            if (app.info.isProfileable()) {
                 runtimeFlags |= Zygote.PROFILEABLE;
             }
             if ("1".equals(SystemProperties.get("debug.checkjni"))) {
@@ -2525,6 +2531,7 @@
     ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
             boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
             int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
+            boolean supplemental, int supplementalUid,
             String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
         long startTime = SystemClock.uptimeMillis();
         ProcessRecord app;
@@ -2618,7 +2625,8 @@
 
         if (app == null) {
             checkSlow(startTime, "startProcess: creating new process record");
-            app = newProcessRecordLocked(info, processName, isolated, isolatedUid, hostingRecord);
+            app = newProcessRecordLocked(info, processName, isolated, isolatedUid, supplemental,
+                    supplementalUid, hostingRecord);
             if (app == null) {
                 Slog.w(TAG, "Failed making new process record for "
                         + processName + "/" + info.uid + " isolated=" + isolated);
@@ -3113,10 +3121,14 @@
 
     @GuardedBy("mService")
     ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
-            boolean isolated, int isolatedUid, HostingRecord hostingRecord) {
+            boolean isolated, int isolatedUid, boolean supplemental, int supplementalUid,
+            HostingRecord hostingRecord) {
         String proc = customProcess != null ? customProcess : info.processName;
         final int userId = UserHandle.getUserId(info.uid);
         int uid = info.uid;
+        if (supplemental) {
+            uid = supplementalUid;
+        }
         if (isolated) {
             if (isolatedUid == 0) {
                 IsolatedUidRange uidRange = getOrCreateIsolatedUidRangeLocked(info, hostingRecord);
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 256cffd..84d2b1f 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -787,7 +787,6 @@
     }
 
     private SparseArray<long[]> getUidProcStateStatsOverTime(long minTime) {
-        final Parcel current = Parcel.obtain();
         final ProcessStats stats = new ProcessStats();
         long curTime;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index d3b5752..711c576 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -94,6 +94,8 @@
     final boolean exported; // from ServiceInfo.exported
     final Runnable restarter; // used to schedule retries of starting the service
     final long createRealTime;  // when this service was created
+    final boolean supplemental; // whether this is a supplemental service
+    final int supplementedAppUid; // the app uid for which this supplemental service is running
     final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
             = new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
                             // All active bindings to the service.
@@ -571,13 +573,13 @@
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
             Runnable restarter) {
         this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg,
-                restarter, null);
+                restarter, null, 0);
     }
 
     ServiceRecord(ActivityManagerService ams, ComponentName name,
             ComponentName instanceName, String definingPackageName, int definingUid,
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
-            Runnable restarter, String supplementalProcessName) {
+            Runnable restarter, String supplementalProcessName, int supplementedAppUid) {
         this.ams = ams;
         this.name = name;
         this.instanceName = instanceName;
@@ -588,6 +590,8 @@
         serviceInfo = sInfo;
         appInfo = sInfo.applicationInfo;
         packageName = sInfo.applicationInfo.packageName;
+        supplemental = supplementalProcessName != null;
+        this.supplementedAppUid = supplementedAppUid;
         if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
             processName = sInfo.processName + ":" + instanceName.getClassName();
         } else if (supplementalProcessName != null) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 40fda4c..cebcc64f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -4549,6 +4549,26 @@
             return new PackageVerificationResult(null,
                     /* isAttributionTagValid */ true);
         }
+        if (Process.isSupplemental(uid)) {
+            // Supplemental processes run in their own UID range, but their associated
+            // UID for checks should always be the UID of the supplemental package.
+            // TODO: We will need to modify the callers of this function instead, so
+            // modifications and checks against the app ops state are done with the
+            // correct UID.
+            try {
+                final PackageManager pm = mContext.getPackageManager();
+                final String supplementalPackageName = pm.getSupplementalProcessPackageName();
+                if (Objects.equals(packageName, supplementalPackageName)) {
+                    int supplementalAppId = pm.getPackageUid(supplementalPackageName,
+                            PackageManager.PackageInfoFlags.of(0));
+                    uid = UserHandle.getUid(UserHandle.getUserId(uid), supplementalAppId);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen for the supplemental package
+                e.printStackTrace();
+            }
+        }
+
 
         // Do not check if uid/packageName/attributionTag is already known.
         synchronized (this) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 05955c3..d2c6c13 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -22,6 +22,7 @@
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 import static android.media.AudioManager.STREAM_SYSTEM;
 import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.INVALID_UID;
 import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
 import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
 import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
@@ -147,6 +148,7 @@
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.MathUtils;
@@ -329,6 +331,9 @@
     private static final int MSG_ROUTING_UPDATED = 41;
     private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
     private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43;
+    private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44;
+    private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
+    private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
 
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -341,6 +346,9 @@
     // retry delay in case of failure to indicate system ready to AudioFlinger
     private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000;
 
+    // List of empty UIDs used to reset the active assistant list
+    private static final int[] NO_ACTIVE_ASSISTANT_SERVICE_UIDS = new int[0];
+
     /** @see AudioSystemThread */
     private AudioSystemThread mAudioSystemThread;
     /** @see AudioHandler */
@@ -756,10 +764,15 @@
     private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
     private long mLoweredFromNormalToVibrateTime;
 
-    // Uid of the active hotword detection service to check if caller is the one or not.
-    @GuardedBy("mHotwordDetectionServiceUidLock")
-    private int mHotwordDetectionServiceUid = android.os.Process.INVALID_UID;
-    private final Object mHotwordDetectionServiceUidLock = new Object();
+    // Array of Uids of valid assistant services to check if caller is one of them
+    @GuardedBy("mSettingsLock")
+    private final ArraySet<Integer> mAssistantUids = new ArraySet<>();
+    @GuardedBy("mSettingsLock")
+    private int mPrimaryAssistantUid = INVALID_UID;
+
+    // Array of Uids of valid active assistant service to check if caller is one of them
+    @GuardedBy("mSettingsLock")
+    private int[] mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS;
 
     // Array of Uids of valid accessibility services to check if caller is one of them
     private final Object mAccessibilityServiceUidsLock = new Object();
@@ -787,9 +800,6 @@
     private boolean mHomeSoundEffectEnabled;
 
     @GuardedBy("mSettingsLock")
-    private int mAssistantUid;
-
-    @GuardedBy("mSettingsLock")
     private int mCurrentImeUid;
 
     private final Object mSupportedSystemUsagesLock = new Object();
@@ -1395,12 +1405,10 @@
             mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied");
             sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
             sendEnabledSurroundFormats(mContentResolver, true);
-            updateAssistantUId(true);
             AudioSystem.setRttEnabled(mRttEnabled);
+            updateAssistantServicesUidsLocked();
         }
-        synchronized (mHotwordDetectionServiceUidLock) {
-            AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid);
-        }
+
         synchronized (mAccessibilityServiceUidsLock) {
             AudioSystem.setA11yServicesUids(mAccessibilityServiceUids);
         }
@@ -1478,6 +1486,68 @@
         updateVibratorInfos();
     }
 
+    private void onRemoveAssistantServiceUids(int[] uids) {
+        synchronized (mSettingsLock) {
+            removeAssistantServiceUidsLocked(uids);
+        }
+    }
+
+    @GuardedBy("mSettingsLock")
+    private void removeAssistantServiceUidsLocked(int[] uids) {
+        boolean changed = false;
+        for (int index = 0; index < uids.length; index++) {
+            if (!mAssistantUids.remove(uids[index])) {
+                Slog.e(TAG, TextUtils.formatSimple(
+                        "Cannot remove assistant service, uid(%d) not present", uids[index]));
+                continue;
+            }
+            changed = true;
+        }
+        if (changed) {
+            updateAssistantServicesUidsLocked();
+        }
+    }
+
+    private void onAddAssistantServiceUids(int[] uids) {
+        synchronized (mSettingsLock) {
+            addAssistantServiceUidsLocked(uids);
+        }
+    }
+
+    @GuardedBy("mSettingsLock")
+    private void addAssistantServiceUidsLocked(int[] uids) {
+        boolean changed = false;
+        for (int index = 0; index < uids.length; index++) {
+            if (uids[index] == INVALID_UID) {
+                continue;
+            }
+            if (!mAssistantUids.add(uids[index])) {
+                Slog.e(TAG, TextUtils.formatSimple(
+                                "Cannot add assistant service, uid(%d) already present",
+                                uids[index]));
+                continue;
+            }
+            changed = true;
+        }
+        if (changed) {
+            updateAssistantServicesUidsLocked();
+        }
+    }
+
+    @GuardedBy("mSettingsLock")
+    private void updateAssistantServicesUidsLocked() {
+        int[] assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray();
+        AudioSystem.setAssistantServicesUids(assistantUids);
+    }
+
+    private void updateActiveAssistantServiceUids() {
+        int [] activeAssistantServiceUids;
+        synchronized (mSettingsLock) {
+            activeAssistantServiceUids = mActiveAssistantServiceUids;
+        }
+        AudioSystem.setActiveAssistantServicesUids(activeAssistantServiceUids);
+    }
+
     private void onReinitVolumes(@NonNull String caller) {
         final int numStreamTypes = AudioSystem.getNumStreamTypes();
         // keep track of any error during stream volume initialization
@@ -2243,8 +2313,7 @@
 
     @GuardedBy("mSettingsLock")
     private void updateAssistantUId(boolean forceUpdate) {
-        int assistantUid = 0;
-
+        int assistantUid = INVALID_UID;
         // Consider assistants in the following order of priority:
         // 1) apk in assistant role
         // 2) voice interaction service
@@ -2288,10 +2357,10 @@
                 }
             }
         }
-
-        if (assistantUid != mAssistantUid || forceUpdate) {
-            AudioSystem.setAssistantUid(assistantUid);
-            mAssistantUid = assistantUid;
+        if ((mPrimaryAssistantUid != assistantUid) || forceUpdate) {
+            mAssistantUids.remove(mPrimaryAssistantUid);
+            mPrimaryAssistantUid = assistantUid;
+            addAssistantServiceUidsLocked(new int[]{mPrimaryAssistantUid});
         }
     }
 
@@ -2342,6 +2411,7 @@
             sendEncodedSurroundMode(cr, "readPersistedSettings");
             sendEnabledSurroundFormats(cr, true);
             updateAssistantUId(true);
+            resetActiveAssistantUidsLocked();
             AudioSystem.setRttEnabled(mRttEnabled);
         }
 
@@ -2367,6 +2437,12 @@
         mVolumeController.loadSettings(cr);
     }
 
+    @GuardedBy("mSettingsLock")
+    private void resetActiveAssistantUidsLocked() {
+        mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS;
+        updateActiveAssistantServiceUids();
+    }
+
     private void readUserRestrictions() {
         if (!mSystemServer.isPrivileged()) {
             return;
@@ -2892,8 +2968,9 @@
         int step;
 
         // skip a2dp absolute volume control request when the device
-        // is not an a2dp device
-        if (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+        // is neither an a2dp device nor BLE device
+        if ((!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+                && !AudioSystem.DEVICE_OUT_ALL_BLE_SET.contains(device))
                 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
             return;
         }
@@ -3031,7 +3108,8 @@
             }
 
             if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
-                    && streamType == getBluetoothContextualVolumeStream()) {
+                    && streamType == getBluetoothContextualVolumeStream()
+                    && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                 if (DEBUG_VOL) {
                     Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
                             + newIndex + " stream=" + streamType);
@@ -3631,8 +3709,9 @@
         int oldIndex;
 
         // skip a2dp absolute volume control request when the device
-        // is not an a2dp device
-        if (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+        // is neither an a2dp device nor BLE device
+        if ((!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+                && !AudioSystem.DEVICE_OUT_ALL_BLE_SET.contains(device))
                 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
             return;
         }
@@ -3675,7 +3754,8 @@
             }
 
             if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
-                    && streamType == getBluetoothContextualVolumeStream()) {
+                    && streamType == getBluetoothContextualVolumeStream()
+                    && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                 if (DEBUG_VOL) {
                     Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
                             + index + " stream=" + streamType);
@@ -7843,6 +7923,17 @@
                 case MSG_PERSIST_SPATIAL_AUDIO_ENABLED:
                     onPersistSpatialAudioEnabled(msg.arg1 == 1);
                     break;
+
+                case MSG_ADD_ASSISTANT_SERVICE_UID:
+                    onAddAssistantServiceUids(new int[]{msg.arg1});
+                    break;
+
+                case MSG_REMOVE_ASSISTANT_SERVICE_UID:
+                    onRemoveAssistantServiceUids(new int[]{msg.arg1});
+                    break;
+                case MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID:
+                    updateActiveAssistantServiceUids();
+                    break;
             }
         }
     }
@@ -9351,7 +9442,7 @@
         pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
         pw.print("  mMcc="); pw.println(mMcc);
-        pw.print("  mCameraSoundForced="); pw.println(mCameraSoundForced);
+        pw.print("  mCameraSoundForced="); pw.println(isCameraSoundForced());
         pw.print("  mHasVibrator="); pw.println(mHasVibrator);
         pw.print("  mVolumePolicy="); pw.println(mVolumePolicy);
         pw.print("  mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
@@ -9371,9 +9462,9 @@
                         + " FromRestrictions=" + mMicMuteFromRestrictions
                         + " FromApi=" + mMicMuteFromApi
                         + " from system=" + mMicMuteFromSystemCached);
-        pw.print("\n  mAssistantUid="); pw.println(mAssistantUid);
         pw.print("  mCurrentImeUid="); pw.println(mCurrentImeUid);
         dumpAccessibilityServiceUids(pw);
+        dumpAssistantServicesUids(pw);
 
         dumpAudioPolicies(pw);
         mDynPolicyLogger.dump(pw);
@@ -9416,6 +9507,19 @@
         }
     }
 
+    private void dumpAssistantServicesUids(PrintWriter pw) {
+        synchronized (mSettingsLock) {
+            if (mAssistantUids.size() > 0) {
+                pw.println("  Assistant service UIDs:");
+                for (int uid : mAssistantUids) {
+                    pw.println("  - " + uid);
+                }
+            } else {
+                pw.println("  No Assistant service Uids.");
+            }
+        }
+    }
+
     private void dumpAccessibilityServiceUids(PrintWriter pw) {
         synchronized (mSupportedSystemUsagesLock) {
             if (mAccessibilityServiceUids != null && mAccessibilityServiceUids.length > 0) {
@@ -9714,13 +9818,40 @@
         }
 
         @Override
-        public void setHotwordDetectionServiceUid(int uid) {
-            synchronized (mHotwordDetectionServiceUidLock) {
-                if (mHotwordDetectionServiceUid != uid) {
-                    mHotwordDetectionServiceUid = uid;
-                    AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid);
+        public void addAssistantServiceUid(int uid) {
+            sendMsg(mAudioHandler, MSG_ADD_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE,
+                    uid, 0, null, 0);
+        }
+
+        @Override
+        public void removeAssistantServiceUid(int uid) {
+            sendMsg(mAudioHandler, MSG_REMOVE_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE,
+                    uid, 0, null, 0);
+        }
+
+        @Override
+        public void setActiveAssistantServicesUids(IntArray activeUids) {
+            synchronized (mSettingsLock) {
+                if (activeUids.size() == 0) {
+                    mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS;
+                } else {
+                    boolean changed = (mActiveAssistantServiceUids == null)
+                            || (mActiveAssistantServiceUids.length != activeUids.size());
+                    if (!changed) {
+                        for (int i = 0; i < mActiveAssistantServiceUids.length; i++) {
+                            if (activeUids.get(i) != mActiveAssistantServiceUids[i]) {
+                                changed = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (changed) {
+                        mActiveAssistantServiceUids = activeUids.toArray();
+                    }
                 }
             }
+            sendMsg(mAudioHandler, MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID, SENDMSG_REPLACE,
+                    0, 0, null, 0);
         }
 
         @Override
@@ -11017,6 +11148,59 @@
         return delayMillis;
     }
 
+    /** @see AudioManager#addAssistantServicesUids(int []) */
+    @Override
+    public void addAssistantServicesUids(int [] assistantUids) {
+        enforceModifyAudioRoutingPermission();
+        Objects.requireNonNull(assistantUids);
+
+        synchronized (mSettingsLock) {
+            addAssistantServiceUidsLocked(assistantUids);
+        }
+    }
+
+    /** @see AudioManager#removeAssistantServicesUids(int []) */
+    @Override
+    public void removeAssistantServicesUids(int [] assistantUids) {
+        enforceModifyAudioRoutingPermission();
+        Objects.requireNonNull(assistantUids);
+        synchronized (mSettingsLock) {
+            removeAssistantServiceUidsLocked(assistantUids);
+        }
+    }
+
+    /** @see AudioManager#getAssistantServicesUids() */
+    @Override
+    public int[] getAssistantServicesUids() {
+        enforceModifyAudioRoutingPermission();
+        int [] assistantUids;
+        synchronized (mSettingsLock) {
+            assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray();
+        }
+        return assistantUids;
+    }
+
+    /** @see AudioManager#setActiveAssistantServiceUids(int []) */
+    @Override
+    public void setActiveAssistantServiceUids(int [] activeAssistantUids) {
+        enforceModifyAudioRoutingPermission();
+        synchronized (mSettingsLock) {
+            mActiveAssistantServiceUids = activeAssistantUids;
+        }
+        updateActiveAssistantServiceUids();
+    }
+
+    /** @see AudioManager#getActiveAssistantServiceUids() */
+    @Override
+    public int[] getActiveAssistantServiceUids() {
+        enforceModifyAudioRoutingPermission();
+        int [] activeAssistantUids;
+        synchronized (mSettingsLock) {
+            activeAssistantUids = mActiveAssistantServiceUids.clone();
+        }
+        return activeAssistantUids;
+    }
+
     UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
         return mDeviceBroker.getDeviceSensorUuid(device);
     }
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index f572261..a70b470 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -387,14 +387,6 @@
     }
 
     /**
-     * Same as {@link AudioSystem#setHotwordDetectionServiceUid(int)}
-     * Communicate UID of current HotwordDetectionService to audio policy service.
-     */
-    public int setHotwordDetectionServiceUid(int uid) {
-        return AudioSystem.setHotwordDetectionServiceUid(uid);
-    }
-
-    /**
      * Same as {@link AudioSystem#setCurrentImeUid(int)}
      * Communicate UID of current InputMethodService to audio policy service.
      */
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index c5e266f..c86a8cb 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -44,7 +44,7 @@
     @Nullable Integer getBiometricPromptSessionId();
 
     /** If the display is in AOD. */
-    boolean isAoD();
+    boolean isAod();
 
     /**
      * Subscribe to context changes.
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 70acaff..9d2fde7 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -120,7 +120,7 @@
     @Override
     public OperationContext updateContext(@NonNull OperationContext operationContext,
             boolean isCryptoOperation) {
-        operationContext.isAoD = isAoD();
+        operationContext.isAod = isAod();
         operationContext.isCrypto = isCryptoOperation;
         setFirstSessionId(operationContext);
         return operationContext;
@@ -160,7 +160,7 @@
     }
 
     @Override
-    public boolean isAoD() {
+    public boolean isAod() {
         return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
     }
 
@@ -177,7 +177,7 @@
 
     private void notifySubscribers() {
         mSubscribers.forEach((context, consumer) -> {
-            context.isAoD = isAoD();
+            context.isAod = isAod();
             consumer.accept(context);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index 8965227..d6ca8a6 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -56,7 +56,7 @@
                 -1 /* sensorId */,
                 operationContext.id,
                 sessionType(operationContext.reason),
-                operationContext.isAoD);
+                operationContext.isAod);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -77,7 +77,7 @@
                 ambientLightLux,
                 operationContext.id,
                 sessionType(operationContext.reason),
-                operationContext.isAoD);
+                operationContext.isAod);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
@@ -109,7 +109,7 @@
                 -1 /* sensorId */,
                 operationContext.id,
                 sessionType(operationContext.reason),
-                operationContext.isAoD);
+                operationContext.isAod);
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
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 d26a780..653776b 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
@@ -229,7 +229,7 @@
                 context.y = y;
                 context.minor = minor;
                 context.major = major;
-                context.isAoD = getBiometricContext().isAoD();
+                context.isAod = getBiometricContext().isAod();
                 session.getSession().onPointerDownWithContext(context);
             } else {
                 session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index e21d901..c92d599 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -213,7 +213,7 @@
                 context.y = y;
                 context.minor = minor;
                 context.major = major;
-                context.isAoD = getBiometricContext().isAoD();
+                context.isAod = getBiometricContext().isAod();
                 session.getSession().onPointerDownWithContext(context);
             } else {
                 session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 452c972..6751153 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -204,8 +204,8 @@
 
             @Override
             public void onPointerDownWithContext(PointerContext context) {
-                onPointerDown(
-                        context.pointerId, context.x, context.y, context.minor, context.major);
+                onPointerDown(context.pointerId, (int) context.x, (int) context.y, context.minor,
+                        context.major);
             }
 
             @Override
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 603f206..108e7bc 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -25,7 +25,6 @@
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
 import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
@@ -191,6 +190,7 @@
     class MultipathTracker {
         final Network network;
         final String subscriberId;
+        private final int mSubId;
 
         private long mQuota;
         /** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */
@@ -204,9 +204,8 @@
             this.network = network;
             this.mNetworkCapabilities = new NetworkCapabilities(nc);
             NetworkSpecifier specifier = nc.getNetworkSpecifier();
-            int subId = INVALID_SUBSCRIPTION_ID;
             if (specifier instanceof TelephonyNetworkSpecifier) {
-                subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+                mSubId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
             } else {
                 throw new IllegalStateException(String.format(
                         "Can't get subId from mobile network %s (%s)",
@@ -217,14 +216,14 @@
             if (tele == null) {
                 throw new IllegalStateException(String.format("Missing TelephonyManager"));
             }
-            tele = tele.createForSubscriptionId(subId);
+            tele = tele.createForSubscriptionId(mSubId);
             if (tele == null) {
                 throw new IllegalStateException(String.format(
-                        "Can't get TelephonyManager for subId %d", subId));
+                        "Can't get TelephonyManager for subId %d", mSubId));
             }
 
             subscriberId = Objects.requireNonNull(tele.getSubscriberId(),
-                    "Null subscriber Id for subId " + subId);
+                    "Null subscriber Id for subId " + mSubId);
             mNetworkTemplate = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
                     .setSubscriberIds(Set.of(subscriberId))
                     .setMeteredness(NetworkStats.METERED_YES)
@@ -282,6 +281,7 @@
                     .setSubscriberId(subscriberId)
                     .setRoaming(!nc.hasCapability(NET_CAPABILITY_NOT_ROAMING))
                     .setMetered(!nc.hasCapability(NET_CAPABILITY_NOT_METERED))
+                    .setSubId(mSubId)
                     .build();
         }
 
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 1894c0f..2c6257f 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -2421,10 +2421,10 @@
     public static final int STATISTICS_FILE_ITEM = 101;
 
     private void readStatsParcelLocked(File parcel) {
+        Parcel in = Parcel.obtain();
         try {
             final AtomicFile parcelFile = new AtomicFile(parcel);
             byte[] data = parcelFile.readFully();
-            Parcel in = Parcel.obtain();
             in.unmarshall(data, 0, data.length);
             in.setDataPosition(0);
             int token;
@@ -2452,6 +2452,8 @@
             }
         } catch (IOException e) {
             Slog.i(TAG, "No initial statistics");
+        } finally {
+            in.recycle();
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7f1482e..f5001102 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -78,6 +78,7 @@
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.display.WifiDisplayStatus;
+import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.hardware.input.InputManagerInternal;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionManager;
@@ -1860,10 +1861,10 @@
         return mDisplayModeDirector.getModeSwitchingType();
     }
 
-    private boolean getDisplayDecorationSupportInternal(int displayId) {
+    private DisplayDecorationSupport getDisplayDecorationSupportInternal(int displayId) {
         final IBinder displayToken = getDisplayToken(displayId);
         if (null == displayToken) {
-            return false;
+            return null;
         }
         return SurfaceControl.getDisplayDecorationSupport(displayToken);
     }
@@ -3550,7 +3551,7 @@
         }
 
         @Override // Binder call
-        public boolean getDisplayDecorationSupport(int displayId) {
+        public DisplayDecorationSupport getDisplayDecorationSupport(int displayId) {
             final long token = Binder.clearCallingIdentity();
             try {
                 return getDisplayDecorationSupportInternal(displayId);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d71e07a..418e91d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -75,6 +75,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * Controls the power state of the display.
@@ -236,42 +237,42 @@
 
     // True if we should fade the screen while turning it off, false if we should play
     // a stylish color fade animation instead.
-    private boolean mColorFadeFadesConfig;
+    private final boolean mColorFadeFadesConfig;
 
     // True if we need to fake a transition to off when coming out of a doze state.
     // Some display hardware will blank itself when coming out of doze in order to hide
     // artifacts. For these displays we fake a transition into OFF so that policy can appropriately
     // blank itself and begin an appropriate power on animation.
-    private boolean mDisplayBlanksAfterDozeConfig;
+    private final boolean mDisplayBlanksAfterDozeConfig;
 
     // True if there are only buckets of brightness values when the display is in the doze state,
     // rather than a full range of values. If this is true, then we'll avoid animating the screen
     // brightness since it'd likely be multiple jarring brightness transitions instead of just one
     // to reach the final state.
-    private boolean mBrightnessBucketsInDozeConfig;
+    private final boolean mBrightnessBucketsInDozeConfig;
 
     // The pending power request.
     // Initially null until the first call to requestPowerState.
-    // Guarded by mLock.
+    @GuardedBy("mLock")
     private DisplayPowerRequest mPendingRequestLocked;
 
     // True if a request has been made to wait for the proximity sensor to go negative.
-    // Guarded by mLock.
+    @GuardedBy("mLock")
     private boolean mPendingWaitForNegativeProximityLocked;
 
     // True if the pending power request or wait for negative proximity flag
     // has been changed since the last update occurred.
-    // Guarded by mLock.
+    @GuardedBy("mLock")
     private boolean mPendingRequestChangedLocked;
 
     // Set to true when the important parts of the pending power request have been applied.
     // The important parts are mainly the screen state.  Brightness changes may occur
     // concurrently.
-    // Guarded by mLock.
+    @GuardedBy("mLock")
     private boolean mDisplayReadyLocked;
 
     // Set to true if a power state update is required.
-    // Guarded by mLock.
+    @GuardedBy("mLock")
     private boolean mPendingUpdatePowerStateLocked;
 
     /* The following state must only be accessed by the handler thread. */
@@ -352,8 +353,8 @@
     // information.
     // At the time of this writing, this value is changed within updatePowerState() only, which is
     // limited to the thread used by DisplayControllerHandler.
-    private BrightnessReason mBrightnessReason = new BrightnessReason();
-    private BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
+    private final BrightnessReason mBrightnessReason = new BrightnessReason();
+    private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
 
     // Brightness animation ramp rates in brightness units per second
     private float mBrightnessRampRateFastDecrease;
@@ -849,6 +850,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void sendUpdatePowerStateLocked() {
         if (!mStopped && !mPendingUpdatePowerStateLocked) {
             mPendingUpdatePowerStateLocked = true;
@@ -2318,6 +2320,7 @@
         return mAutomaticBrightnessController.convertToNits(brightness);
     }
 
+    @GuardedBy("mLock")
     private void updatePendingProximityRequestsLocked() {
         mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
         mPendingWaitForNegativeProximityLocked = false;
@@ -2421,12 +2424,7 @@
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
 
-        mHandler.runWithScissors(new Runnable() {
-            @Override
-            public void run() {
-                dumpLocal(pw);
-            }
-        }, 1000);
+        mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
     }
 
     private void dumpLocal(PrintWriter pw) {
@@ -2458,6 +2456,9 @@
         pw.println("  mAppliedThrottling=" + mAppliedThrottling);
         pw.println("  mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
         pw.println("  mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
+        pw.println("  mAppliedTemporaryAutoBrightnessAdjustment="
+                + mAppliedTemporaryAutoBrightnessAdjustment);
+        pw.println("  mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
         pw.println("  mDozing=" + mDozing);
         pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
         pw.println("  mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
@@ -2465,21 +2466,21 @@
         pw.println("  mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker);
         pw.println("  mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker);
         pw.println("  mPendingScreenOff=" + mPendingScreenOff);
-        pw.println("  mReportedToPolicy=" +
-                reportedToPolicyToString(mReportedScreenStateToPolicy));
+        pw.println("  mReportedToPolicy="
+                + reportedToPolicyToString(mReportedScreenStateToPolicy));
 
         if (mScreenBrightnessRampAnimator != null) {
-            pw.println("  mScreenBrightnessRampAnimator.isAnimating()=" +
-                    mScreenBrightnessRampAnimator.isAnimating());
+            pw.println("  mScreenBrightnessRampAnimator.isAnimating()="
+                    + mScreenBrightnessRampAnimator.isAnimating());
         }
 
         if (mColorFadeOnAnimator != null) {
-            pw.println("  mColorFadeOnAnimator.isStarted()=" +
-                    mColorFadeOnAnimator.isStarted());
+            pw.println("  mColorFadeOnAnimator.isStarted()="
+                    + mColorFadeOnAnimator.isStarted());
         }
         if (mColorFadeOffAnimator != null) {
-            pw.println("  mColorFadeOffAnimator.isStarted()=" +
-                    mColorFadeOffAnimator.isStarted());
+            pw.println("  mColorFadeOffAnimator.isStarted()="
+                    + mColorFadeOffAnimator.isStarted());
         }
 
         if (mPowerState != null) {
@@ -2605,7 +2606,7 @@
         }
     }
 
-    private final void logHbmBrightnessStats(float brightness, int displayStatsId) {
+    private void logHbmBrightnessStats(float brightness, int displayStatsId) {
         synchronized (mHandler) {
             FrameworkStatsLog.write(
                     FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
@@ -2824,7 +2825,7 @@
 
         @Override
         public boolean equals(Object obj) {
-            if (obj == null || !(obj instanceof BrightnessReason)) {
+            if (!(obj instanceof BrightnessReason)) {
                 return false;
             }
             BrightnessReason other = (BrightnessReason) obj;
@@ -2832,6 +2833,11 @@
         }
 
         @Override
+        public int hashCode() {
+            return Objects.hash(reason, modifier);
+        }
+
+        @Override
         public String toString() {
             return toString(0);
         }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c02e725..d233c5e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -182,8 +182,11 @@
         private final long mPhysicalDisplayId;
         private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
         private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
+        private final DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
+                new DisplayModeDirector.DesiredDisplayModeSpecs();
         private final boolean mIsDefaultDisplay;
         private final BacklightAdapter mBacklightAdapter;
+        private final SidekickInternal mSidekickInternal;
 
         private DisplayDeviceInfo mInfo;
         private boolean mHavePendingChanges;
@@ -200,8 +203,6 @@
         private int mActiveDisplayModeAtStartId = INVALID_MODE_ID;
         private Display.Mode mUserPreferredMode;
         private int mActiveModeId = INVALID_MODE_ID;
-        private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
-                new DisplayModeDirector.DesiredDisplayModeSpecs();
         private boolean mDisplayModeSpecsInvalid;
         private int mActiveColorMode;
         private Display.HdrCapabilities mHdrCapabilities;
@@ -210,13 +211,11 @@
         private boolean mAllmRequested;
         private boolean mGameContentTypeRequested;
         private boolean mSidekickActive;
-        private SidekickInternal mSidekickInternal;
         private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
         // The supported display modes according to SurfaceFlinger
         private SurfaceControl.DisplayMode[] mSfDisplayModes;
         // The active display mode in SurfaceFlinger
         private SurfaceControl.DisplayMode mActiveSfDisplayMode;
-        private DisplayDeviceConfig mDisplayDeviceConfig;
 
         private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
                 new DisplayEventReceiver.FrameRateOverride[0];
@@ -233,7 +232,6 @@
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
             mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay,
                     mSurfaceControlProxy);
-            mDisplayDeviceConfig = null;
             mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
         }
 
@@ -459,9 +457,6 @@
             final Context context = getOverlayContext();
             mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId,
                     mIsDefaultDisplay);
-            if (mDisplayDeviceConfig == null) {
-                return;
-            }
 
             // Load brightness HWC quirk
             mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk(
@@ -1083,8 +1078,8 @@
             pw.println("mGameContentTypeRequested=" + mGameContentTypeRequested);
             pw.println("mStaticDisplayInfo=" + mStaticDisplayInfo);
             pw.println("mSfDisplayModes=");
-            for (int i = 0; i < mSfDisplayModes.length; i++) {
-                pw.println("  " + mSfDisplayModes[i]);
+            for (SurfaceControl.DisplayMode sfDisplayMode : mSfDisplayModes) {
+                pw.println("  " + sfDisplayMode);
             }
             pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
             pw.println("mSupportedModes=");
@@ -1238,6 +1233,8 @@
     }
 
     public static class Injector {
+        // Native callback.
+        @SuppressWarnings("unused")
         private ProxyDisplayEventReceiver mReceiver;
         public void setDisplayEventListenerLocked(Looper looper, DisplayEventListener listener) {
             mReceiver = new ProxyDisplayEventReceiver(looper, listener);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 93c73be..6f5729f 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -825,7 +825,6 @@
      *
      * @param device The device to associate with the LogicalDisplay.
      * @param displayId The display ID to give the new display. If invalid, a new ID is assigned.
-     * @param isDefault Indicates if we are creating the default display.
      * @return The new logical display if created, null otherwise.
      */
     private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index d8672fc..2567e43 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -55,7 +55,7 @@
      * If this is the first time the property is being set or if the rate is 0,
      * the value jumps directly to the target.
      *
-     * @param target The target value.
+     * @param targetLinear The target value.
      * @param rate The convergence rate in units per second, or 0 to set the value immediately.
      * @return True if the target differs from the previous target.
      */
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index d48ccd5..025ccd1 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -45,7 +45,6 @@
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.net.Uri;
@@ -70,6 +69,7 @@
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -307,6 +307,7 @@
             }
 
             List<String> appCertificates = getCertificateFingerprint(packageInfo);
+            List<String> appCertificateLineage = getCertificateLineage(packageInfo);
             List<String> installerCertificates =
                     getInstallerCertificateFingerprint(installerPackageName);
 
@@ -314,6 +315,7 @@
 
             builder.setPackageName(getPackageNameNormalized(packageName));
             builder.setAppCertificates(appCertificates);
+            builder.setAppCertificateLineage(appCertificateLineage);
             builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1));
             builder.setInstallerName(getPackageNameNormalized(installerPackageName));
             builder.setInstallerCertificates(installerCertificates);
@@ -460,6 +462,14 @@
         return certificateFingerprints;
     }
 
+    private List<String> getCertificateLineage(@NonNull PackageInfo packageInfo) {
+        ArrayList<String> certificateLineage = new ArrayList();
+        for (Signature signature : getSignatureLineage(packageInfo)) {
+            certificateLineage.add(getFingerprint(signature));
+        }
+        return certificateLineage;
+    }
+
     /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */
     private Map<String, String> getAllowedInstallers(@NonNull PackageInfo packageInfo) {
         Map<String, String> packageCertMap = new HashMap<>();
@@ -541,6 +551,38 @@
         return signingInfo.getApkContentsSigners();
     }
 
+    private static Signature[] getSignatureLineage(@NonNull PackageInfo packageInfo) {
+        // Obtain the signing info of the package.
+        SigningInfo signingInfo = packageInfo.signingInfo;
+        if (signingInfo == null) {
+            throw new IllegalArgumentException(
+                    "Package signature not found in " + packageInfo);
+        }
+
+        // Obtain the active signatures of the package.
+        Signature[] signatureLineage = getSignatures(packageInfo);
+
+        // Obtain the past signatures of the package.
+        if (!signingInfo.hasMultipleSigners() && signingInfo.hasPastSigningCertificates()) {
+            Signature[] pastSignatures = signingInfo.getSigningCertificateHistory();
+
+            // Merge the signatures and return.
+            Signature[] allSignatures =
+                    new Signature[signatureLineage.length + pastSignatures.length];
+            int i;
+            for (i = 0; i < signatureLineage.length; i++) {
+                allSignatures[i] = signatureLineage[i];
+            }
+            for (int j = 0; j < pastSignatures.length; j++) {
+                allSignatures[i] = pastSignatures[j];
+                i++;
+            }
+            signatureLineage = allSignatures;
+        }
+
+        return signatureLineage;
+    }
+
     private static String getFingerprint(Signature cert) {
         InputStream input = new ByteArrayInputStream(cert.toByteArray());
 
diff --git a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java
new file mode 100644
index 0000000..6b442a6
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.logcat;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.ServiceManager;
+import android.os.logcat.ILogcatManagerService;
+import android.util.Slog;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+
+/**
+ * This dialog is shown to the user before an activity in a harmful app is launched.
+ *
+ * See {@code PackageManager.setLogcatAppInfo} for more info.
+ */
+public class LogAccessConfirmationActivity extends AlertActivity implements
+        DialogInterface.OnClickListener {
+    private static final String TAG = LogAccessConfirmationActivity.class.getSimpleName();
+
+    private String mPackageName;
+    private IntentSender mTarget;
+    private final ILogcatManagerService mLogcatManagerService =
+            ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat"));
+
+    private int mUid;
+    private int mGid;
+    private int mPid;
+    private int mFd;
+
+    private static final String EXTRA_UID = "uid";
+    private static final String EXTRA_GID = "gid";
+    private static final String EXTRA_PID = "pid";
+    private static final String EXTRA_FD = "fd";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Intent intent = getIntent();
+        mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        mUid = intent.getIntExtra("uid", 0);
+        mGid = intent.getIntExtra("gid", 0);
+        mPid = intent.getIntExtra("pid", 0);
+        mFd = intent.getIntExtra("fd", 0);
+
+        final AlertController.AlertParams p = mAlertParams;
+        p.mTitle = getString(R.string.log_access_confirmation_title);
+        p.mView = createView();
+
+        p.mPositiveButtonText = getString(R.string.log_access_confirmation_allow);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = getString(R.string.log_access_confirmation_deny);
+        p.mNegativeButtonListener = this;
+
+        mAlert.installContent(mAlertParams);
+    }
+
+    private View createView() {
+        final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog,
+                null /*root*/);
+        ((TextView) view.findViewById(R.id.app_name_text))
+                .setText(mPackageName);
+        ((TextView) view.findViewById(R.id.message))
+                .setText(getIntent().getExtras().getString("body"));
+        return view;
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case DialogInterface.BUTTON_POSITIVE:
+                try {
+                    mLogcatManagerService.approve(mUid, mGid, mPid, mFd);
+                } catch (Throwable t) {
+                    Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+                }
+                finish();
+                break;
+            case DialogInterface.BUTTON_NEGATIVE:
+                try {
+                    mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
+                } catch (Throwable t) {
+                    Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+                }
+                finish();
+                break;
+        }
+    }
+
+    /**
+     * Create the Intent for a LogAccessConfirmationActivity.
+     */
+    public static Intent createIntent(Context context, String targetPackageName,
+            IntentSender target, int uid, int gid, int pid, int fd) {
+        final Intent intent = new Intent();
+        intent.setClass(context, LogAccessConfirmationActivity.class);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+        intent.putExtra(EXTRA_UID, uid);
+        intent.putExtra(EXTRA_GID, gid);
+        intent.putExtra(EXTRA_PID, pid);
+        intent.putExtra(EXTRA_FD, fd);
+
+        return intent;
+    }
+
+}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index ff6372ae..34614d5 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -16,20 +16,36 @@
 
 package com.android.server.logcat;
 
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ActivityManagerInternal;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.ILogd;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.os.logcat.ILogcatManagerService;
 import android.util.Slog;
 
+import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
+import java.util.Arrays;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 /**
- * Service responsible for manage the access to Logcat.
+ * Service responsible for managing the access to Logcat.
  */
 public final class LogcatManagerService extends SystemService {
 
@@ -38,6 +54,43 @@
     private final BinderService mBinderService;
     private final ExecutorService mThreadExecutor;
     private ILogd mLogdService;
+    private NotificationManager mNotificationManager;
+    private @NonNull ActivityManager mActivityManager;
+    private ActivityManagerInternal mActivityManagerInternal;
+    private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2;
+    private static int sUidImportanceListenerCount = 0;
+    private static final int AID_SHELL_UID = 2000;
+
+    // TODO This allowlist is just a temporary workaround for the tests:
+    //      FrameworksServicesTests
+    //      PlatformRuleTests
+    // After adapting the test suites, the allowlist will be removed in
+    // the upcoming bug fix patches.
+    private static final String[] ALLOWABLE_TESTING_PACKAGES = {
+            "android.platform.test.rule.tests",
+            "com.android.frameworks.servicestests"
+    };
+
+    // TODO Same as the above ALLOWABLE_TESTING_PACKAGES.
+    private boolean isAllowableTestingPackage(int uid) {
+        PackageManager pm = mContext.getPackageManager();
+
+        String[] packageNames = pm.getPackagesForUid(uid);
+
+        if (ArrayUtils.isEmpty(packageNames)) {
+            return false;
+        }
+
+        for (String name : packageNames) {
+            Slog.e(TAG, "isAllowableTestingPackage: " + name);
+
+            if (Arrays.asList(ALLOWABLE_TESTING_PACKAGES).contains(name)) {
+                return true;
+            }
+        }
+
+        return false;
+    };
 
     private final class BinderService extends ILogcatManagerService.Stub {
         @Override
@@ -51,6 +104,197 @@
             // the logd data access is finished.
             mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false));
         }
+
+        @Override
+        public void approve(int uid, int gid, int pid, int fd) {
+            try {
+                getLogdService().approve(uid, gid, pid, fd);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void decline(int uid, int gid, int pid, int fd) {
+            try {
+                getLogdService().decline(uid, gid, pid, fd);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private ILogd getLogdService() {
+        synchronized (LogcatManagerService.this) {
+            if (mLogdService == null) {
+                LogcatManagerService.this.addLogdService();
+            }
+            return mLogdService;
+        }
+    }
+
+    private String getBodyString(Context context, String callingPackage, int uid) {
+        PackageManager pm = context.getPackageManager();
+        try {
+            return context.getString(
+                com.android.internal.R.string.log_access_confirmation_body,
+                pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO,
+                    UserHandle.getUserId(uid)).loadLabel(pm));
+        } catch (NameNotFoundException e) {
+            // App name is unknown.
+            return null;
+        }
+    }
+
+    private void sendNotification(int notificationId, String clientInfo, int uid, int gid, int pid,
+            int fd) {
+
+        final ActivityManagerInternal activityManagerInternal =
+                LocalServices.getService(ActivityManagerInternal.class);
+
+        PackageManager pm = mContext.getPackageManager();
+        String packageName = activityManagerInternal.getPackageNameByPid(pid);
+        if (packageName != null) {
+            String notificationBody = getBodyString(mContext, packageName, uid);
+
+            final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext,
+                    packageName, null, uid, gid, pid, fd);
+
+            if (notificationBody == null) {
+                // Decline the logd access if the nofitication body is unknown
+                Slog.e(TAG, "Unknown notification body, declining the logd access");
+                declineLogdAccess(uid, gid, pid, fd);
+                return;
+            }
+
+            // TODO Next version will replace notification with dialogue
+            // per UX guidance.
+            generateNotificationWithBodyContent(notificationId, clientInfo, notificationBody,
+                    mIntent);
+            return;
+
+        }
+
+        String[] packageNames = pm.getPackagesForUid(uid);
+
+        if (ArrayUtils.isEmpty(packageNames)) {
+            // Decline the logd access if the app name is unknown
+            Slog.e(TAG, "Unknown calling package name, declining the logd access");
+            declineLogdAccess(uid, gid, pid, fd);
+            return;
+        }
+
+        String firstPackageName = packageNames[0];
+
+        if (firstPackageName == null || firstPackageName.length() == 0) {
+            // Decline the logd access if the package name from uid is unknown
+            Slog.e(TAG, "Unknown calling package name, declining the logd access");
+            declineLogdAccess(uid, gid, pid, fd);
+            return;
+        }
+
+        String notificationBody = getBodyString(mContext, firstPackageName, uid);
+
+        final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext,
+                firstPackageName, null, uid, gid, pid, fd);
+
+        if (notificationBody == null) {
+            Slog.e(TAG, "Unknown notification body, declining the logd access");
+            declineLogdAccess(uid, gid, pid, fd);
+            return;
+        }
+
+        // TODO Next version will replace notification with dialogue
+        // per UX guidance.
+        generateNotificationWithBodyContent(notificationId, clientInfo,
+                notificationBody, mIntent);
+    }
+
+    private void declineLogdAccess(int uid, int gid, int pid, int fd) {
+        try {
+            getLogdService().decline(uid, gid, pid, fd);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Fails to call remote functions ", ex);
+        }
+    }
+
+    private void generateNotificationWithBodyContent(int notificationId, String clientInfo,
+            String notificationBody, Intent intent) {
+        final Notification.Builder notificationBuilder = new Notification.Builder(
+                mContext,
+                SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY);
+        intent.setFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.setIdentifier(String.valueOf(notificationId) + clientInfo);
+        intent.putExtra("body", notificationBody);
+
+        notificationBuilder
+            .setSmallIcon(R.drawable.ic_info)
+            .setContentTitle(
+                mContext.getString(R.string.log_access_confirmation_title))
+            .setContentText(notificationBody)
+            .setContentIntent(
+                PendingIntent.getActivity(mContext, 0, intent,
+                    PendingIntent.FLAG_IMMUTABLE))
+            .setTicker(mContext.getString(R.string.log_access_confirmation_title))
+            .setOnlyAlertOnce(true)
+            .setAutoCancel(true);
+        mNotificationManager.notify(notificationId, notificationBuilder.build());
+    }
+
+    /**
+     * A class which watches an uid for background access and notifies the logdMonitor when
+     * the package status becomes foreground (importance change)
+     */
+    private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
+        private final int mExpectedUid;
+        private final int mExpectedGid;
+        private final int mExpectedPid;
+        private final int mExpectedFd;
+        private int mExpectedImportance;
+        private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE;
+
+        UidImportanceListener(int uid, int gid, int pid, int fd, int importance) {
+            mExpectedUid = uid;
+            mExpectedGid = gid;
+            mExpectedPid = pid;
+            mExpectedFd = fd;
+            mExpectedImportance = importance;
+        }
+
+        @Override
+        public void onUidImportance(int uid, int importance) {
+            if (uid == mExpectedUid) {
+                mCurrentImportance = importance;
+
+                /**
+                 * 1) If the process status changes to foreground, send a notification
+                 * for user consent.
+                 * 2) If the process status remains background, we decline logd access request.
+                 **/
+                if (importance <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+                    String clientInfo = getClientInfo(uid, mExpectedGid, mExpectedPid, mExpectedFd);
+                    sendNotification(0, clientInfo, uid, mExpectedGid, mExpectedPid,
+                            mExpectedFd);
+                    mActivityManager.removeOnUidImportanceListener(this);
+
+                    synchronized (LogcatManagerService.this) {
+                        sUidImportanceListenerCount--;
+                    }
+                } else {
+                    try {
+                        getLogdService().decline(uid, mExpectedGid, mExpectedPid, mExpectedFd);
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "Fails to call remote functions ", ex);
+                    }
+                }
+            }
+        }
+    }
+
+    private static String getClientInfo(int uid, int gid, int pid, int fd) {
+        return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID="
+            + Integer.toString(pid) + " FD=" + Integer.toString(fd);
     }
 
     private class LogdMonitor implements Runnable {
@@ -74,9 +318,7 @@
         }
 
         /**
-         * The current version grant the permission by default.
-         * And track the logd access.
-         * The next version will generate a prompt for users.
+         * LogdMonitor generates a prompt for users.
          * The users decide whether the logd access is allowed.
          */
         @Override
@@ -86,11 +328,14 @@
             }
 
             if (mStart) {
+
+                // TODO Temporarily approve all the requests to unblock testing failures.
                 try {
-                    mLogdService.approve(mUid, mGid, mPid, mFd);
-                } catch (RemoteException ex) {
-                    Slog.e(TAG, "Fails to call remote functions ", ex);
+                    getLogdService().approve(mUid, mGid, mPid, mFd);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
                 }
+                return;
             }
         }
     }
@@ -100,6 +345,8 @@
         mContext = context;
         mBinderService = new BinderService();
         mThreadExecutor = Executors.newCachedThreadPool();
+        mActivityManager = context.getSystemService(ActivityManager.class);
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
     }
 
     @Override
@@ -114,5 +361,4 @@
     private void addLogdService() {
         mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd"));
     }
-
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index b66c466..33ac6cd 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -16,14 +16,16 @@
 package com.android.server.net;
 
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
-import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
-import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -328,6 +330,8 @@
                 return FIREWALL_CHAIN_NAME_POWERSAVE;
             case FIREWALL_CHAIN_RESTRICTED:
                 return FIREWALL_CHAIN_NAME_RESTRICTED;
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
             default:
                 return String.valueOf(chain);
         }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 8ef42ff..3cb5878 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -91,4 +91,10 @@
      */
     public abstract void setMeteredRestrictedPackagesAsync(
             Set<String> packageNames, int userId);
+
+    /** Informs that Low Power Standby has become active */
+    public abstract void setLowPowerStandbyActive(boolean active);
+
+    /** Informs that the Low Power Standby allowlist has changed */
+    public abstract void setLowPowerStandbyAllowlist(int[] uids);
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index bb22902..8d05415 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -46,22 +46,23 @@
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
-import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -70,11 +71,13 @@
 import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM;
 import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_USER_EXEMPTED;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
@@ -88,6 +91,7 @@
 import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
 import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
+import static android.net.NetworkPolicyManager.isProcStateAllowedWhileInLowPowerStandby;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
 import static android.net.NetworkPolicyManager.resolveNetworkId;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
@@ -171,11 +175,9 @@
 import android.net.NetworkPolicyManager;
 import android.net.NetworkPolicyManager.UidState;
 import android.net.NetworkRequest;
-import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkTemplate;
-import android.net.TelephonyNetworkSpecifier;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.BestClock;
@@ -478,6 +480,8 @@
     volatile boolean mRestrictBackgroundChangedInBsm;
     @GuardedBy("mUidRulesFirstLock")
     volatile boolean mRestrictedNetworkingMode;
+    @GuardedBy("mUidRulesFirstLock")
+    volatile boolean mLowPowerStandbyActive;
 
     private final boolean mSuppressDefaultPolicy;
 
@@ -517,6 +521,8 @@
     final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
+    @GuardedBy("mUidRulesFirstLock")
+    final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
 
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mUidRulesFirstLock")
@@ -545,6 +551,9 @@
     @GuardedBy("mUidRulesFirstLock")
     private final SparseBooleanArray mPowerSaveTempWhitelistAppIds = new SparseBooleanArray();
 
+    @GuardedBy("mUidRulesFirstLock")
+    private final SparseBooleanArray mLowPowerStandbyAllowlistUids = new SparseBooleanArray();
+
     /**
      * UIDs that have been allowlisted temporarily to be able to have network access despite being
      * idle. Other power saving restrictions still apply.
@@ -1508,7 +1517,8 @@
                     .setType(TYPE_MOBILE)
                     .setSubscriberId(subscriberId)
                     .setMetered(true)
-                    .setDefaultNetwork(true).build();
+                    .setDefaultNetwork(true)
+                    .setSubId(subId).build();
             if (template.matches(probeIdent)) {
                 return subId;
             }
@@ -1745,7 +1755,8 @@
                 .setType(TYPE_MOBILE)
                 .setSubscriberId(subscriberId)
                 .setMetered(true)
-                .setDefaultNetwork(true).build();
+                .setDefaultNetwork(true)
+                .setSubId(subId).build();
         for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
             final NetworkTemplate template = mNetworkPolicy.keyAt(i);
             if (template.matches(probeIdent)) {
@@ -1977,7 +1988,8 @@
                             .setType(TYPE_MOBILE)
                             .setSubscriberId(subscriberId)
                             .setMetered(true)
-                            .setDefaultNetwork(true).build();
+                            .setDefaultNetwork(true)
+                            .setSubId(subId).build();
                     // Template is matched when subscriber id matches.
                     if (template.matches(probeIdent)) {
                         matchingSubIds.add(subId);
@@ -2079,7 +2091,8 @@
         mNetIdToSubId.clear();
         final ArrayMap<NetworkStateSnapshot, NetworkIdentity> identified = new ArrayMap<>();
         for (final NetworkStateSnapshot snapshot : snapshots) {
-            mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot));
+            final int subId = snapshot.getSubId();
+            mNetIdToSubId.put(snapshot.getNetwork().getNetId(), subId);
 
             // Policies matched by NPMS only match by subscriber ID or by network ID.
             final NetworkIdentity ident = new NetworkIdentity.Builder()
@@ -2284,7 +2297,8 @@
                 .setType(TYPE_MOBILE)
                 .setSubscriberId(subscriberId)
                 .setMetered(true)
-                .setDefaultNetwork(true).build();
+                .setDefaultNetwork(true)
+                .setSubId(subId).build();
         for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
             final NetworkTemplate template = mNetworkPolicy.keyAt(i);
             if (template.matches(probeIdent)) {
@@ -3763,6 +3777,7 @@
                 fout.print("Restrict power: "); fout.println(mRestrictPower);
                 fout.print("Device idle: "); fout.println(mDeviceIdleMode);
                 fout.print("Restricted networking mode: "); fout.println(mRestrictedNetworkingMode);
+                fout.print("Low Power Standby mode: "); fout.println(mLowPowerStandbyActive);
                 synchronized (mMeteredIfacesLock) {
                     fout.print("Metered ifaces: ");
                     fout.println(mMeteredIfaces);
@@ -3898,6 +3913,18 @@
                     fout.decreaseIndent();
                 }
 
+                size = mLowPowerStandbyAllowlistUids.size();
+                if (size > 0) {
+                    fout.println("Low Power Standby allowlist uids:");
+                    fout.increaseIndent();
+                    for (int i = 0; i < size; i++) {
+                        fout.print("UID=");
+                        fout.print(mLowPowerStandbyAllowlistUids.keyAt(i));
+                        fout.println();
+                    }
+                    fout.decreaseIndent();
+                }
+
                 final SparseBooleanArray knownUids = new SparseBooleanArray();
                 collectKeys(mUidState, knownUids);
                 collectKeys(mUidBlockedState, knownUids);
@@ -3979,6 +4006,12 @@
         return isProcStateAllowedWhileIdleOrPowerSaveMode(uidState);
     }
 
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isUidTop(int uid) {
+        final UidState uidState = mUidState.get(uid);
+        return isProcStateAllowedWhileInLowPowerStandby(uidState);
+    }
+
     /**
      * Process state of UID changed; if needed, will trigger
      * {@link #updateRulesForDataUsageRestrictionsUL(int)} and
@@ -3995,8 +4028,10 @@
                 // state changed, push updated rules
                 mUidState.put(uid, newUidState);
                 updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState);
-                if (isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
-                        != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState)) {
+                boolean allowedWhileIdleOrPowerSaveModeChanged =
+                        isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
+                                != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState);
+                if (allowedWhileIdleOrPowerSaveModeChanged) {
                     updateRuleForAppIdleUL(uid);
                     if (mDeviceIdleMode) {
                         updateRuleForDeviceIdleUL(uid);
@@ -4006,6 +4041,17 @@
                     }
                     updateRulesForPowerRestrictionsUL(uid);
                 }
+                if (mLowPowerStandbyActive) {
+                    boolean allowedInLpsChanged =
+                            isProcStateAllowedWhileInLowPowerStandby(oldUidState)
+                                    != isProcStateAllowedWhileInLowPowerStandby(newUidState);
+                    if (allowedInLpsChanged) {
+                        if (!allowedWhileIdleOrPowerSaveModeChanged) {
+                            updateRulesForPowerRestrictionsUL(uid);
+                        }
+                        updateRuleForLowPowerStandbyUL(uid);
+                    }
+                }
                 return true;
             }
         } finally {
@@ -4029,6 +4075,9 @@
                     updateRuleForRestrictPowerUL(uid);
                 }
                 updateRulesForPowerRestrictionsUL(uid);
+                if (mLowPowerStandbyActive) {
+                    updateRuleForLowPowerStandbyUL(uid);
+                }
                 return true;
             }
         }
@@ -4232,6 +4281,50 @@
         }
     }
 
+    @GuardedBy("mUidRulesFirstLock")
+    void updateRulesForLowPowerStandbyUL() {
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForLowPowerStandbyUL");
+        try {
+            if (mLowPowerStandbyActive) {
+                mUidFirewallLowPowerStandbyModeRules.clear();
+                for (int i = mUidState.size() - 1; i >= 0; i--) {
+                    final int uid = mUidState.keyAt(i);
+                    UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+                    if (hasInternetPermissionUL(uid) && uidBlockedState != null
+                            && (uidBlockedState.effectiveBlockedReasons
+                                    & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) {
+                        mUidFirewallLowPowerStandbyModeRules.put(mUidBlockedState.keyAt(i),
+                                FIREWALL_RULE_ALLOW);
+                    }
+                }
+                setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                        mUidFirewallLowPowerStandbyModeRules, CHAIN_TOGGLE_ENABLE);
+            } else {
+                setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, null, CHAIN_TOGGLE_DISABLE);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+        }
+    }
+
+    @GuardedBy("mUidRulesFirstLock")
+    void updateRuleForLowPowerStandbyUL(int uid) {
+        if (!hasInternetPermissionUL(uid)) {
+            return;
+        }
+
+        final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+        if (mUidState.contains(uid) && uidBlockedState != null
+                && (uidBlockedState.effectiveBlockedReasons & BLOCKED_REASON_LOW_POWER_STANDBY)
+                == 0) {
+            mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
+            setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW);
+        } else {
+            mUidFirewallLowPowerStandbyModeRules.delete(uid);
+            setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+        }
+    }
+
     /**
      * Returns whether a uid is allowlisted from power saving restrictions (eg: Battery Saver, Doze
      * mode, and app idle).
@@ -4261,6 +4354,14 @@
         return mPowerSaveWhitelistExceptIdleAppIds.get(appId);
     }
 
+    /**
+     * Returns whether a uid is allowlisted from low power standby restrictions.
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isAllowlistedFromLowPowerStandbyUL(int uid) {
+        return mLowPowerStandbyAllowlistUids.get(uid);
+    }
+
     // NOTE: since both fw_dozable and fw_powersave uses the same map
     // (mPowerSaveTempWhitelistAppIds) for allowlisting, we can reuse their logic in this method.
     @GuardedBy("mUidRulesFirstLock")
@@ -4598,6 +4699,7 @@
         mPowerSaveTempWhitelistAppIds.delete(uid);
         mAppIdleTempWhitelistAppIds.delete(uid);
         mUidFirewallRestrictedModeRules.delete(uid);
+        mUidFirewallLowPowerStandbyModeRules.delete(uid);
         synchronized (mUidStateCallbackInfos) {
             mUidStateCallbackInfos.remove(uid);
         }
@@ -4823,6 +4925,7 @@
         }
 
         final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
+        final boolean isTop = isUidTop(uid);
 
         final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
 
@@ -4836,17 +4939,21 @@
         int newAllowedReasons = ALLOWED_REASON_NONE;
         newBlockedReasons |= (mRestrictPower ? BLOCKED_REASON_BATTERY_SAVER : 0);
         newBlockedReasons |= (mDeviceIdleMode ? BLOCKED_REASON_DOZE : 0);
+        newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
         newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
         newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
 
         newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
         newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
+        newAllowedReasons |= (isTop ? ALLOWED_REASON_TOP : 0);
         newAllowedReasons |= (isWhitelistedFromPowerSaveUL(uid, true)
                 ? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0);
         newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid)
                 ? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0);
         newAllowedReasons |= (uidBlockedState.allowedReasons
                 & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
+        newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
+                ? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
 
         uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
                 & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
@@ -4868,6 +4975,7 @@
                     + ", mRestrictPower: " + mRestrictPower
                     + ", mDeviceIdleMode: " + mDeviceIdleMode
                     + ", isForeground=" + isForeground
+                    + ", isTop=" + isTop
                     + ", isWhitelisted=" + isWhitelisted
                     + ", oldUidBlockedState=" + previousUidBlockedState.toString()
                     + ", newUidBlockedState=" + uidBlockedState.toString());
@@ -5398,6 +5506,8 @@
                 mUidFirewallPowerSaveRules.put(uid, rule);
             } else if (chain == FIREWALL_CHAIN_RESTRICTED) {
                 mUidFirewallRestrictedModeRules.put(uid, rule);
+            } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
+                mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
             }
 
             try {
@@ -5445,6 +5555,9 @@
                     .setFirewallUidRule(FIREWALL_CHAIN_POWERSAVE, uid, FIREWALL_RULE_DEFAULT);
             mNetworkManager
                     .setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, uid, FIREWALL_RULE_DEFAULT);
+            mNetworkManager
+                    .setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid,
+                            FIREWALL_RULE_DEFAULT);
             mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
             mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
         } catch (IllegalStateException e) {
@@ -5720,6 +5833,67 @@
             mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
                     userId, 0, packageNames).sendToTarget();
         }
+
+        @Override
+        public void setLowPowerStandbyActive(boolean active) {
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setLowPowerStandbyActive");
+            try {
+                synchronized (mUidRulesFirstLock) {
+                    if (mLowPowerStandbyActive == active) {
+                        return;
+                    }
+                    mLowPowerStandbyActive = active;
+                    synchronized (mNetworkPoliciesSecondLock) {
+                        if (!mSystemReady) return;
+                    }
+
+                    forEachUid("updateRulesForRestrictPower",
+                            uid -> updateRulesForPowerRestrictionsUL(uid));
+                    updateRulesForLowPowerStandbyUL();
+                }
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+            }
+        }
+
+        @Override
+        public void setLowPowerStandbyAllowlist(int[] uids) {
+            synchronized (mUidRulesFirstLock) {
+                final SparseBooleanArray changedUids = new SparseBooleanArray();
+                for (int i = 0; i < mLowPowerStandbyAllowlistUids.size(); i++) {
+                    final int oldUid = mLowPowerStandbyAllowlistUids.keyAt(i);
+                    if (!ArrayUtils.contains(uids, oldUid)) {
+                        changedUids.put(oldUid, true);
+                    }
+                }
+
+                for (int i = 0; i < changedUids.size(); i++) {
+                    final int deletedUid = changedUids.keyAt(i);
+                    mLowPowerStandbyAllowlistUids.delete(deletedUid);
+                }
+
+                for (int newUid : uids) {
+                    if (mLowPowerStandbyAllowlistUids.indexOfKey(newUid) < 0) {
+                        changedUids.append(newUid, true);
+                        mLowPowerStandbyAllowlistUids.append(newUid, true);
+                    }
+                }
+
+                if (!mLowPowerStandbyActive) {
+                    return;
+                }
+
+                synchronized (mNetworkPoliciesSecondLock) {
+                    if (!mSystemReady) return;
+                }
+
+                for (int i = 0; i < changedUids.size(); i++) {
+                    final int changedUid = changedUids.keyAt(i);
+                    updateRulesForPowerRestrictionsUL(changedUid);
+                    updateRuleForLowPowerStandbyUL(changedUid);
+                }
+            }
+        }
     }
 
     private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
@@ -5747,17 +5921,6 @@
         }
     }
 
-    private int parseSubId(@NonNull NetworkStateSnapshot snapshot) {
-        int subId = INVALID_SUBSCRIPTION_ID;
-        if (snapshot.getNetworkCapabilities().hasTransport(TRANSPORT_CELLULAR)) {
-            NetworkSpecifier spec = snapshot.getNetworkCapabilities().getNetworkSpecifier();
-            if (spec instanceof TelephonyNetworkSpecifier) {
-                subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
-            }
-        }
-        return subId;
-    }
-
     @GuardedBy("mNetworkPoliciesSecondLock")
     private int getSubIdLocked(Network network) {
         return mNetIdToSubId.get(network.getNetId(), INVALID_SUBSCRIPTION_ID);
@@ -5888,6 +6051,9 @@
                 effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_USER_RESTRICTED;
             }
+            if ((allowedReasons & ALLOWED_REASON_TOP) != 0) {
+                effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
+            }
             if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_ALLOWLIST) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
@@ -5903,6 +6069,10 @@
             if ((allowedReasons & ALLOWED_METERED_REASON_USER_EXEMPTED) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
             }
+            if ((allowedReasons & ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST) != 0) {
+                effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
+            }
+
             return effectiveBlockedReasons;
         }
 
@@ -5927,6 +6097,7 @@
                 BLOCKED_REASON_DOZE,
                 BLOCKED_REASON_APP_STANDBY,
                 BLOCKED_REASON_RESTRICTED_MODE,
+                BLOCKED_REASON_LOW_POWER_STANDBY,
                 BLOCKED_METERED_REASON_DATA_SAVER,
                 BLOCKED_METERED_REASON_USER_RESTRICTED,
                 BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -5935,9 +6106,11 @@
         private static final int[] ALLOWED_REASONS = {
                 ALLOWED_REASON_SYSTEM,
                 ALLOWED_REASON_FOREGROUND,
+                ALLOWED_REASON_TOP,
                 ALLOWED_REASON_POWER_SAVE_ALLOWLIST,
                 ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
                 ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
+                ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST,
                 ALLOWED_METERED_REASON_USER_EXEMPTED,
                 ALLOWED_METERED_REASON_SYSTEM,
                 ALLOWED_METERED_REASON_FOREGROUND,
@@ -5955,6 +6128,8 @@
                     return "APP_STANDBY";
                 case BLOCKED_REASON_RESTRICTED_MODE:
                     return "RESTRICTED_MODE";
+                case BLOCKED_REASON_LOW_POWER_STANDBY:
+                    return "LOW_POWER_STANDBY";
                 case BLOCKED_METERED_REASON_DATA_SAVER:
                     return "DATA_SAVER";
                 case BLOCKED_METERED_REASON_USER_RESTRICTED:
@@ -5975,12 +6150,16 @@
                     return "SYSTEM";
                 case ALLOWED_REASON_FOREGROUND:
                     return "FOREGROUND";
+                case ALLOWED_REASON_TOP:
+                    return "TOP";
                 case ALLOWED_REASON_POWER_SAVE_ALLOWLIST:
                     return "POWER_SAVE_ALLOWLIST";
                 case ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST:
                     return "POWER_SAVE_EXCEPT_IDLE_ALLOWLIST";
                 case ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS:
                     return "RESTRICTED_MODE_PERMISSIONS";
+                case ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST:
+                    return "LOW_POWER_STANDBY_ALLOWLIST";
                 case ALLOWED_METERED_REASON_USER_EXEMPTED:
                     return "METERED_USER_EXEMPTED";
                 case ALLOWED_METERED_REASON_SYSTEM:
@@ -6047,7 +6226,8 @@
 
             int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
                     | BLOCKED_REASON_DOZE
-                    | BLOCKED_REASON_BATTERY_SAVER;
+                    | BLOCKED_REASON_BATTERY_SAVER
+                    | BLOCKED_REASON_LOW_POWER_STANDBY;
             if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
                 uidRule |= RULE_REJECT_ALL;
             } else if ((blockedReasons & powerBlockedReasons) != 0) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2717f0c..3d34976 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5805,6 +5805,7 @@
                             || channel.isImportanceLockedByCriticalDeviceFunction());
             final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
             userId = adjustedSbn.getUser().getIdentifier();
+            int uid =  adjustedSbn.getUid();
             ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
             if (summaries == null) {
                 summaries = new ArrayMap<>();
@@ -5851,7 +5852,7 @@
                         notificationRecord.getIsAppImportanceLocked());
                 summaries.put(pkg, summarySbn.getKey());
             }
-            if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
+            if (summaryRecord != null && checkDisqualifyingFeatures(userId, uid,
                     summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
                     true)) {
                 return summaryRecord;
@@ -6527,7 +6528,7 @@
 
     @VisibleForTesting
     protected void fixNotification(Notification notification, String pkg, String tag, int id,
-            int userId) throws NameNotFoundException {
+            int userId) throws NameNotFoundException, RemoteException {
         final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                 pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                 (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
@@ -6561,6 +6562,21 @@
             actions.toArray(notification.actions);
         }
 
+        // Ensure MediaStyle has correct permissions for remote device extras
+        if (notification.isStyle(Notification.MediaStyle.class)) {
+            int hasMediaContentControlPermission = mPackageManager.checkPermission(
+                    android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId);
+            if (hasMediaContentControlPermission != PERMISSION_GRANTED) {
+                notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_DEVICE);
+                notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_ICON);
+                notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_INTENT);
+                if (DBG) {
+                    Slog.w(TAG, "Package " + pkg + ": Use of setRemotePlayback requires the "
+                            + "MEDIA_CONTENT_CONTROL permission");
+                }
+            }
+        }
+
         // Remote views? Are they too big?
         checkRemoteViews(pkg, tag, id, notification);
     }
@@ -6822,7 +6838,6 @@
             return false;
         }
 
-
         // blocked apps
         boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid);
         synchronized (mNotificationLock) {
@@ -7215,10 +7230,12 @@
                 if (mAssistants.isEnabled()) {
                     mAssistants.onNotificationEnqueuedLocked(r);
                     mHandler.postDelayed(
-                            new PostNotificationRunnable(r.getKey(), enqueueElapsedTimeMs),
+                            new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                                    r.getUid(), enqueueElapsedTimeMs),
                             DELAY_FOR_ASSISTANT_TIME);
                 } else {
-                    mHandler.post(new PostNotificationRunnable(r.getKey(), enqueueElapsedTimeMs));
+                    mHandler.post(new PostNotificationRunnable(r.getKey(),
+                            r.getSbn().getPackageName(), r.getUid(), enqueueElapsedTimeMs));
                 }
             }
         }
@@ -7242,16 +7259,19 @@
     protected class PostNotificationRunnable implements Runnable {
         private final String key;
         private final long postElapsedTimeMs;
+        private final String pkg;
+        private final int uid;
 
-        PostNotificationRunnable(String key, @ElapsedRealtimeLong long postElapsedTimeMs) {
+        PostNotificationRunnable(String key, String pkg, int uid,
+                @ElapsedRealtimeLong long postElapsedTimeMs) {
             this.key = key;
+            this.pkg = pkg;
+            this.uid = uid;
             this.postElapsedTimeMs = postElapsedTimeMs;
         }
 
         @Override
         public void run() {
-            String pkg = StatusBarNotification.getPkgFromKey(key);
-            int uid = StatusBarNotification.getUidFromKey(key);
             boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
             synchronized (mNotificationLock) {
                 try {
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 5d18069..86ac7c1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -179,7 +179,7 @@
         assertFlag();
         final long callingId = Binder.clearCallingIdentity();
         try {
-            if (grant) {
+            if (grant && !reviewRequired) {
                 mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
             } else {
                 mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId,
@@ -210,8 +210,10 @@
         if (pkgPerm == null || pkgPerm.packageName == null) {
             return;
         }
-        setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
-                pkgPerm.userSet, !pkgPerm.userSet);
+        if (!isPermissionFixed(pkgPerm.packageName, pkgPerm.userId)) {
+            setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
+                    pkgPerm.userSet, !pkgPerm.userSet);
+        }
     }
 
     public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
@@ -239,7 +241,8 @@
             try {
                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
                         userId);
-                return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+                return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
+                        | PackageManager.FLAG_PERMISSION_USER_FIXED)) != 0;
             } catch (RemoteException e) {
                 Slog.e(TAG, "Could not reach system server", e);
             }
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index c942a43..78aa45a 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2134,6 +2134,9 @@
     private String[] getPackagesForUidInternal(int uid, int callingUid) {
         final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
         final int userId = UserHandle.getUserId(uid);
+        if (Process.isSupplemental(uid)) {
+            uid = getSupplementalProcessUid();
+        }
         final int appId = UserHandle.getAppId(uid);
         return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
     }
@@ -2369,6 +2372,10 @@
     }
 
     public final boolean isCallerSameApp(String packageName, int uid) {
+        if (Process.isSupplemental(uid)) {
+            return (packageName != null
+                    && packageName.equals(mService.getSupplementalProcessPackageName()));
+        }
         AndroidPackage pkg = mPackages.get(packageName);
         return pkg != null
                 && UserHandle.getAppId(uid) == pkg.getUid();
@@ -4288,6 +4295,9 @@
         if (getInstantAppPackageName(callingUid) != null) {
             return null;
         }
+        if (Process.isSupplemental(uid)) {
+            uid = getSupplementalProcessUid();
+        }
         final int callingUserId = UserHandle.getUserId(callingUid);
         final int appId = UserHandle.getAppId(uid);
         final Object obj = mSettings.getSettingBase(appId);
@@ -4320,7 +4330,11 @@
         final int callingUserId = UserHandle.getUserId(callingUid);
         final String[] names = new String[uids.length];
         for (int i = uids.length - 1; i >= 0; i--) {
-            final int appId = UserHandle.getAppId(uids[i]);
+            int uid = uids[i];
+            if (Process.isSupplemental(uid)) {
+                uid = getSupplementalProcessUid();
+            }
+            final int appId = UserHandle.getAppId(uid);
             final Object obj = mSettings.getSettingBase(appId);
             if (obj instanceof SharedUserSetting) {
                 final SharedUserSetting sus = (SharedUserSetting) obj;
@@ -4366,6 +4380,9 @@
         if (getInstantAppPackageName(callingUid) != null) {
             return 0;
         }
+        if (Process.isSupplemental(uid)) {
+            uid = getSupplementalProcessUid();
+        }
         final int callingUserId = UserHandle.getUserId(callingUid);
         final int appId = UserHandle.getAppId(uid);
         final Object obj = mSettings.getSettingBase(appId);
@@ -4391,6 +4408,9 @@
         if (getInstantAppPackageName(callingUid) != null) {
             return 0;
         }
+        if (Process.isSupplemental(uid)) {
+            uid = getSupplementalProcessUid();
+        }
         final int callingUserId = UserHandle.getUserId(callingUid);
         final int appId = UserHandle.getAppId(uid);
         final Object obj = mSettings.getSettingBase(appId);
@@ -4415,6 +4435,9 @@
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return false;
         }
+        if (Process.isSupplemental(uid)) {
+            uid = getSupplementalProcessUid();
+        }
         final int appId = UserHandle.getAppId(uid);
         final Object obj = mSettings.getSettingBase(appId);
         if (obj instanceof SharedUserSetting) {
@@ -5539,6 +5562,9 @@
 
     @Override
     public int getUidTargetSdkVersion(int uid) {
+        if (Process.isSupplemental(uid)) {
+            uid = getSupplementalProcessUid();
+        }
         final int appId = UserHandle.getAppId(uid);
         final SettingBase settingBase = mSettings.getSettingBase(appId);
         if (settingBase instanceof SharedUserSetting) {
@@ -5565,6 +5591,9 @@
     @Nullable
     @Override
     public ArrayMap<String, ProcessInfo> getProcessesForUid(int uid) {
+        if (Process.isSupplemental(uid)) {
+            uid = getSupplementalProcessUid();
+        }
         final int appId = UserHandle.getAppId(uid);
         final SettingBase settingBase = mSettings.getSettingBase(appId);
         if (settingBase instanceof SharedUserSetting) {
@@ -5594,4 +5623,8 @@
             return null;
         }
     }
+
+    private int getSupplementalProcessUid() {
+        return getPackage(mService.getSupplementalProcessPackageName()).getUid();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index e657838..6b3ce77 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_MUTABLE;
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -1218,8 +1219,9 @@
         }
 
         @Override
-        public PendingIntent getActivityLaunchIntent(ComponentName component, Bundle opts,
+        public PendingIntent getActivityLaunchIntent(String callingPackage, ComponentName component,
                 UserHandle user) {
+            ensureShortcutPermission(callingPackage);
             if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
                 throw new ActivityNotFoundException("Activity could not be found");
             }
@@ -1237,7 +1239,7 @@
                 // calling identity to mirror the startActivityAsUser() call which does not validate
                 // the calling user
                 return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, launchIntent,
-                        FLAG_IMMUTABLE, null /* options */, user);
+                        FLAG_MUTABLE, null /* opts */, user);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 279de83..0575b8c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -51,13 +51,6 @@
 import android.content.pm.UserInfo;
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.overlay.OverlayPaths;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.PackageUserStateUtils;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -118,10 +111,18 @@
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.PackageUserStateUtils;
 import com.android.server.pm.pkg.SuspendParams;
+import com.android.server.pm.pkg.component.ParsedComponent;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedProcess;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
 import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.verify.domain.DomainVerificationPersistence;
+import com.android.server.utils.Slogf;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -982,7 +983,8 @@
                     Slog.i(PackageManagerService.TAG, "Stopping package " + pkgName, e);
                 }
                 List<UserInfo> users = getAllUsers(userManager);
-                final int installUserId = installUser != null ? installUser.getIdentifier() : 0;
+                int installUserId = installUser != null ? installUser.getIdentifier()
+                        : UserHandle.USER_SYSTEM;
                 if (users != null && allowInstall) {
                     for (UserInfo user : users) {
                         // By default we consider this app to be installed
@@ -993,8 +995,14 @@
                         // user we are installing for.
                         final boolean installed = installUser == null
                                 || (installUserId == UserHandle.USER_ALL
-                                    && !isAdbInstallDisallowed(userManager, user.id))
+                                    && !isAdbInstallDisallowed(userManager, user.id)
+                                    && !user.preCreated)
                                 || installUserId == user.id;
+                        if (DEBUG_MU) {
+                            Slogf.d(TAG, "createNewSetting(pkg=%s, installUserId=%s, user=%s, "
+                                    + "installed=%b)",
+                                    pkgName, installUserId, user.toFullString(), installed);
+                        }
                         pkgSetting.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,
                                 installed,
                                 true /*stopped*/,
@@ -2039,11 +2047,14 @@
 
             serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
 
-            if (DEBUG_MU) Log.i(TAG, "Writing " + userPackagesStateFile);
+            if (DEBUG_MU) {
+                Slogf.i(TAG, "Writing %s (%d packages)", userPackagesStateFile,
+                        mPackages.values().size());
+            }
             for (final PackageSetting pkg : mPackages.values()) {
                 final PackageUserStateInternal ustate = pkg.readUserState(userId);
                 if (DEBUG_MU) {
-                    Log.i(TAG, "  pkg=" + pkg.getPackageName()
+                    Log.v(TAG, "  pkg=" + pkg.getPackageName()
                             + ", installed=" + ustate.isInstalled()
                             + ", state=" + ustate.getEnabledState());
                 }
@@ -5617,7 +5628,8 @@
                                 userId);
                         packageSetting.setInstallPermissionsFixed(true);
                     } else if (packageSetting.getSharedUser() == null && !isUpgradeToR) {
-                        Slog.w(TAG, "Missing permission state for package: " + packageName);
+                        Slogf.w(TAG, "Missing permission state for package %s on user %d",
+                                packageName, userId);
                         packageSetting.getLegacyPermissionState().setMissing(true, userId);
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index ed351fd..e9074c4 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.permission;
 
 import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
@@ -752,11 +753,14 @@
             flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
             flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
             flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
             flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
             flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
             flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
             flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
+            // REVIEW_REQUIRED can only be set by non-system apps for for POST_NOTIFICATIONS
+            if (!POST_NOTIFICATIONS.equals(permName)) {
+                flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+            }
         }
 
         final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
index c2b3cbc..0320818 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
@@ -20,6 +20,8 @@
 import android.annotation.Nullable;
 import android.content.pm.ActivityInfo;
 
+import java.util.Set;
+
 /** @hide **/
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedActivity extends ParsedMainComponent {
@@ -59,6 +61,12 @@
     @Nullable
     String getPermission();
 
+    /**
+     * Gets the trusted host certificates of apps that are allowed to embed this activity.
+     */
+    @NonNull
+    Set<String> getKnownActivityEmbeddingCerts();
+
     int getPersistableMode();
 
     int getPrivateFlags();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
index 91c0b07..acd5a81 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 
 import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
+import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForStringSet;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,12 +34,17 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Set;
+
 /**
  * @hide
  **/
@@ -63,6 +69,8 @@
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
     private String permission;
+    @Nullable
+    private Set<String> mKnownActivityEmbeddingCerts;
 
     private int launchMode;
     private int documentLaunchMode;
@@ -113,6 +121,7 @@
         this.rotationAnimation = other.rotationAnimation;
         this.colorMode = other.colorMode;
         this.windowLayout = other.windowLayout;
+        this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts;
     }
 
     /**
@@ -239,6 +248,25 @@
         return this;
     }
 
+    @NonNull
+    @Override
+    public Set<String> getKnownActivityEmbeddingCerts() {
+        return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
+                : mKnownActivityEmbeddingCerts;
+    }
+
+    /**
+     * Sets the trusted host certificates of apps that are allowed to embed this activity.
+     */
+    public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) {
+        // Convert the provided digest to upper case for consistent Set membership
+        // checks when verifying the signing certificate digests of requesting apps.
+        this.mKnownActivityEmbeddingCerts = new ArraySet<>();
+        for (String knownCert : knownActivityEmbeddingCerts) {
+            this.mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
+        }
+    }
+
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("Activity{");
@@ -287,6 +315,7 @@
         } else {
             dest.writeBoolean(false);
         }
+        sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags);
     }
 
     public ParsedActivityImpl() {
@@ -320,6 +349,7 @@
         if (in.readBoolean()) {
             windowLayout = new ActivityInfo.WindowLayout(in);
         }
+        this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
     }
 
     @NonNull
@@ -344,7 +374,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -360,6 +390,7 @@
             @Nullable String taskAffinity,
             int privateFlags,
             @Nullable String permission,
+            @Nullable Set<String> knownActivityEmbeddingCerts,
             int launchMode,
             int documentLaunchMode,
             int maxRecents,
@@ -383,6 +414,7 @@
         this.taskAffinity = taskAffinity;
         this.privateFlags = privateFlags;
         this.permission = permission;
+        this.mKnownActivityEmbeddingCerts = knownActivityEmbeddingCerts;
         this.launchMode = launchMode;
         this.documentLaunchMode = documentLaunchMode;
         this.maxRecents = maxRecents;
@@ -645,10 +677,10 @@
     }
 
     @DataClass.Generated(
-            time = 1641431949361L,
+            time = 1644372875433L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java",
-            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity, android.os.Parcelable]\[email protected](genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java",
+            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\[email protected](genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index a6c22a18..bbbf598 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -21,6 +21,7 @@
 
 import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
 import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -151,7 +152,8 @@
                                         | flag(ActivityInfo.FLAG_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_showWhenLocked, sa)
                                         | flag(ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE, R.styleable.AndroidManifestActivity_supportsPictureInPicture, sa)
                                         | flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa)
-                                        | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa)));
+                                        | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa))
+                                        | flag(ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING, R.styleable.AndroidManifestActivity_allowUntrustedActivityEmbedding, sa));
 
                 activity.setPrivateFlags(activity.getPrivateFlags() | (flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED,
                                         R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa)
@@ -338,6 +340,20 @@
             activity.setPermission(permission != null ? permission : pkg.getPermission());
         }
 
+        final ParseResult<Set<String>> knownActivityEmbeddingCertsResult =
+                parseKnownActivityEmbeddingCerts(array, resources, isAlias
+                        ? R.styleable.AndroidManifestActivityAlias_knownActivityEmbeddingCerts
+                        : R.styleable.AndroidManifestActivity_knownActivityEmbeddingCerts, input);
+        if (knownActivityEmbeddingCertsResult.isError()) {
+            return input.error(knownActivityEmbeddingCertsResult);
+        } else {
+            final Set<String> knownActivityEmbeddingCerts = knownActivityEmbeddingCertsResult
+                    .getResult();
+            if (knownActivityEmbeddingCerts != null) {
+                activity.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts);
+            }
+        }
+
         final boolean setExported = array.hasValue(exportedAttr);
         if (setExported) {
             activity.setExported(array.getBoolean(exportedAttr, false));
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
index b92b845..f199841 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
@@ -549,6 +549,7 @@
             ai.metaData = a.getMetaData();
         }
         ai.applicationInfo = applicationInfo;
+        ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
         return ai;
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 52d9b7a..1754877 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -381,6 +381,12 @@
 
     ParsingPackage setLocaleConfigRes(int localeConfigRes);
 
+    /**
+     * Sets the trusted host certificates of apps that are allowed to embed activities of this
+     * application.
+     */
+    ParsingPackage setKnownActivityEmbeddingCerts(Set<String> knownActivityEmbeddingCerts);
+
     // TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
     //  for moving to the next step
     @CallSuper
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 84422cd..c4b1275 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -563,6 +563,9 @@
         return (mBooleans & flag) != 0;
     }
 
+    @Nullable
+    private Set<String> mKnownActivityEmbeddingCerts;
+
     // Derived fields
     @NonNull
     private UUID mStorageUuid;
@@ -1150,6 +1153,9 @@
         appInfo.setVersionCode(mLongVersionCode);
         appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
         appInfo.setLocaleConfigRes(mLocaleConfigRes);
+        if (mKnownActivityEmbeddingCerts != null) {
+            appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts);
+        }
 
         return appInfo;
     }
@@ -1329,6 +1335,7 @@
         dest.writeInt(this.nativeHeapZeroInitialized);
         sForBoolean.parcel(this.requestRawExternalStorageAccess, dest, flags);
         dest.writeInt(this.mLocaleConfigRes);
+        sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
     }
 
     public ParsingPackageImpl(Parcel in) {
@@ -1477,6 +1484,7 @@
         this.nativeHeapZeroInitialized = in.readInt();
         this.requestRawExternalStorageAccess = sForBoolean.unparcel(in);
         this.mLocaleConfigRes = in.readInt();
+        this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
         assignDerivedFields();
     }
 
@@ -2376,6 +2384,13 @@
         return getBoolean(Booleans.RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED);
     }
 
+    @NonNull
+    @Override
+    public Set<String> getKnownActivityEmbeddingCerts() {
+        return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
+                : mKnownActivityEmbeddingCerts;
+    }
+
     @Override
     public boolean shouldInheritKeyStoreKeys() {
         return getBoolean(Booleans.INHERIT_KEYSTORE_KEYS);
@@ -2973,4 +2988,11 @@
         mLocaleConfigRes = value;
         return this;
     }
+
+    @Override
+    public ParsingPackage setKnownActivityEmbeddingCerts(
+            @Nullable Set<String> knownEmbeddingCerts) {
+        mKnownActivityEmbeddingCerts = knownEmbeddingCerts;
+        return this;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index e8f03ff..a891980 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -30,6 +30,8 @@
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
+import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
+
 import android.annotation.AnyRes;
 import android.annotation.CheckResult;
 import android.annotation.IntDef;
@@ -2009,6 +2011,19 @@
                                 .AndroidManifestApplication_requestForegroundServiceExemption,
                         false));
             }
+            final ParseResult<Set<String>> knownActivityEmbeddingCertsResult =
+                    parseKnownActivityEmbeddingCerts(sa, res,
+                            R.styleable.AndroidManifestApplication_knownActivityEmbeddingCerts,
+                            input);
+            if (knownActivityEmbeddingCertsResult.isError()) {
+                return input.error(knownActivityEmbeddingCertsResult);
+            } else {
+                final Set<String> knownActivityEmbeddingCerts = knownActivityEmbeddingCertsResult
+                        .getResult();
+                if (knownActivityEmbeddingCerts != null) {
+                    pkg.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts);
+                }
+            }
         } finally {
             sa.recycle();
         }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
index 9430e98..cb474df 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
@@ -22,6 +22,8 @@
 import android.annotation.Nullable;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -38,6 +40,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /** @hide **/
 public class ParsingUtils {
@@ -168,4 +171,50 @@
             return list;
         }
     }
+
+    /**
+     * Parse the {@link android.R.attr#knownActivityEmbeddingCerts} attribute, if available.
+     */
+    @NonNull
+    public static ParseResult<Set<String>> parseKnownActivityEmbeddingCerts(@NonNull TypedArray sa,
+            @NonNull Resources res, int resourceId, @NonNull ParseInput input) {
+        if (!sa.hasValue(resourceId)) {
+            return input.success(null);
+        }
+
+        final int knownActivityEmbeddingCertsResource = sa.getResourceId(resourceId, 0);
+        if (knownActivityEmbeddingCertsResource != 0) {
+            // The knownCerts attribute supports both a string array resource as well as a
+            // string resource for the case where the permission should only be granted to a
+            // single known signer.
+            Set<String> knownEmbeddingCertificates = null;
+            final String resourceType = res.getResourceTypeName(
+                    knownActivityEmbeddingCertsResource);
+            if (resourceType.equals("array")) {
+                final String[] knownCerts = res.getStringArray(knownActivityEmbeddingCertsResource);
+                if (knownCerts != null) {
+                    knownEmbeddingCertificates = Set.of(knownCerts);
+                }
+            } else {
+                final String knownCert = res.getString(knownActivityEmbeddingCertsResource);
+                if (knownCert != null) {
+                    knownEmbeddingCertificates = Set.of(knownCert);
+                }
+            }
+            if (knownEmbeddingCertificates == null || knownEmbeddingCertificates.isEmpty()) {
+                return input.error("Defined a knownActivityEmbeddingCerts attribute but the "
+                        + "provided resource is null");
+            }
+            return input.success(knownEmbeddingCertificates);
+        }
+
+        // If the knownCerts resource ID is null - the app specified a string value for the
+        // attribute representing a single trusted signer.
+        final String knownCert = sa.getString(resourceId);
+        if (knownCert == null || knownCert.isEmpty()) {
+            return input.error("Defined a knownActivityEmbeddingCerts attribute but the provided "
+                    + "string is empty");
+        }
+        return input.success(Set.of(knownCert));
+    }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
index 2ef90ac..3205b76 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
@@ -21,6 +21,8 @@
 import android.content.pm.ApplicationInfo;
 import android.util.SparseArray;
 
+import java.util.Set;
+
 /**
  * Container for fields that are eventually exposed through {@link ApplicationInfo}.
  * <p>
@@ -285,6 +287,13 @@
     String getPermission();
 
     /**
+     * @see ApplicationInfo#knownActivityEmbeddingCerts
+     * @see R.styleable#AndroidManifestApplication_knownActivityEmbeddingCerts
+     */
+    @NonNull
+    Set<String> getKnownActivityEmbeddingCerts();
+
+    /**
      * @see ApplicationInfo#processName
      * @see R.styleable#AndroidManifestApplication_process
      */
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index c9a8701..c637c67 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -37,7 +37,7 @@
 import android.app.TaskInfo;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -58,6 +58,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.permission.PermissionControllerManager;
+import android.permission.PermissionManager;
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.telecom.TelecomManager;
@@ -141,7 +142,7 @@
      * This change reflects the presence of the new Notification Permission
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
     private static final long NOTIFICATION_PERM_CHANGE_ID = 194833441L;
 
     private List<String> mAppOpPermissions;
@@ -149,7 +150,6 @@
     private Context mContext;
     private PackageManagerInternal mPackageManagerInternal;
     private NotificationManagerInternal mNotificationManager;
-    private PermissionManagerServiceInternal mPermissionManagerService;
     private final PackageManager mPackageManager;
 
     public PermissionPolicyService(@NonNull Context context) {
@@ -1001,13 +1001,48 @@
 
     private class Internal extends PermissionPolicyInternal {
 
-        private ActivityInterceptorCallback mActivityInterceptorCallback =
+        // UIDs that, if a grant dialog is shown for POST_NOTIFICATIONS before next reboot,
+        // should display a "continue allowing" message, rather than an "allow" message
+        private final ArraySet<Integer> mContinueNotifGrantMessageUids = new ArraySet<>();
+
+        private final ActivityInterceptorCallback mActivityInterceptorCallback =
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
                     public ActivityInterceptorCallback.ActivityInterceptResult intercept(
                             ActivityInterceptorInfo info) {
-                        return null;
+                        String action = info.intent.getAction();
+                        ActivityInterceptResult result = null;
+                        if (!PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)
+                                && !PackageManager.ACTION_REQUEST_PERMISSIONS.equals(action)) {
+                            return null;
+                        }
+                        // Only this interceptor can add LEGACY_ACCESS_PERMISSION_NAMES
+                        if (info.intent.getStringArrayExtra(PackageManager
+                                .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES)
+                                != null) {
+                            result = new ActivityInterceptResult(
+                                    new Intent(info.intent), info.checkedOptions);
+                            result.intent.removeExtra(PackageManager
+                                    .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES);
+                        }
+                        if (PackageManager.ACTION_REQUEST_PERMISSIONS.equals(action)
+                                && !mContinueNotifGrantMessageUids.contains(info.realCallingUid)) {
+                            return result;
+                        }
+                        if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)) {
+                            String otherPkg = info.intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                            if (otherPkg == null || (mPackageManager.getPermissionFlags(
+                                    POST_NOTIFICATIONS, otherPkg, UserHandle.of(info.userId))
+                                    & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+                                return result;
+                            }
+                        }
+
+                        mContinueNotifGrantMessageUids.remove(info.realCallingUid);
+                        return new ActivityInterceptResult(info.intent.putExtra(PackageManager
+                                        .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES,
+                                new String[] { POST_NOTIFICATIONS }), info.checkedOptions);
                     }
 
                     @Override
@@ -1057,12 +1092,21 @@
             launchNotificationPermissionRequestDialog(packageName, user, taskId);
         }
 
-        private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle userId) {
-            if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, userId)) {
+        private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) {
+            if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, user)
+                    || ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user)
+                    & FLAG_PERMISSION_REVIEW_REQUIRED) == 0)) {
                 return;
             }
-            mPackageManager.updatePermissionFlags(POST_NOTIFICATIONS, packageName,
-                    FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId);
+            try {
+                int uid = mPackageManager.getPackageUidAsUser(packageName, 0,
+                        user.getIdentifier());
+                mContinueNotifGrantMessageUids.add(uid);
+                mPackageManager.updatePermissionFlags(POST_NOTIFICATIONS, packageName,
+                        FLAG_PERMISSION_REVIEW_REQUIRED, 0, user);
+            } catch (PackageManager.NameNotFoundException e) {
+                // Do nothing
+            }
         }
 
         private void launchNotificationPermissionRequestDialog(String pkgName, UserHandle user,
@@ -1142,8 +1186,10 @@
             if (pkg == null || pkg.getPackageName() == null
                     || Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName())
                     || pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
-                Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
-                        + pkgName + " or pkg is Permission Controller");
+                if (pkg == null) {
+                    Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
+                            + pkgName);
+                }
                 return false;
             }
 
@@ -1175,9 +1221,10 @@
             }
             boolean hasCreatedNotificationChannels = mNotificationManager
                     .getNumNotificationChannelsForPackage(pkg.getPackageName(), uid, true) > 0;
-            boolean needsReview = (mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName,
-                    user) & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
-            return hasCreatedNotificationChannels && needsReview;
+            int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
+            boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
+            boolean needsReview = (flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
+            return hasCreatedNotificationChannels && (needsReview || !explicitlySet);
         }
     }
 }
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index cea84b5..2d2bad2 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -42,6 +42,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.net.NetworkPolicyManagerInternal;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -394,7 +395,11 @@
     /** Notify other system components about the updated Low Power Standby active state */
     private void notifyActiveChanged(boolean active) {
         final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        final NetworkPolicyManagerInternal npmi = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
+
         pmi.setLowPowerStandbyActive(active);
+        npmi.setLowPowerStandbyActive(active);
     }
 
     @VisibleForTesting
@@ -580,7 +585,10 @@
 
     private void notifyAllowlistChanged(int[] allowlistUids) {
         final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        final NetworkPolicyManagerInternal npmi = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
         pmi.setLowPowerStandbyAllowlist(allowlistUids);
+        npmi.setLowPowerStandbyAllowlist(allowlistUids);
     }
 
     private final class LocalService extends LowPowerStandbyControllerInternal {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index f6a9359..adee325 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -2392,7 +2392,8 @@
                         metrics.unaccountedKb,
                         metrics.gpuTotalUsageKb,
                         metrics.gpuPrivateAllocationsKb,
-                        metrics.dmaBufTotalExportedKb));
+                        metrics.dmaBufTotalExportedKb,
+                        metrics.shmemKb));
         return StatsManager.PULL_SUCCESS;
     }
 
@@ -4490,6 +4491,9 @@
     }
 
     int pullMediaCapabilitiesStats(int atomTag, List<StatsEvent> pulledData) {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+            return StatsManager.PULL_SKIP;
+        }
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (audioManager == null) {
             return StatsManager.PULL_SKIP;
diff --git a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
index 30b6e68..9ebf59e 100644
--- a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
+++ b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
@@ -82,6 +82,7 @@
         result.vmallocUsedKb = (int) mInfos[Debug.MEMINFO_VM_ALLOC_USED];
         result.pageTablesKb = (int) mInfos[Debug.MEMINFO_PAGE_TABLES];
         result.kernelStackKb = (int) mInfos[Debug.MEMINFO_KERNEL_STACK];
+        result.shmemKb = (int) mInfos[Debug.MEMINFO_SHMEM];
         result.totalIonKb = totalIonKb;
         result.gpuTotalUsageKb = gpuTotalUsageKb;
         result.gpuPrivateAllocationsKb = gpuPrivateAllocationsKb;
@@ -95,6 +96,7 @@
         public int vmallocUsedKb;
         public int pageTablesKb;
         public int kernelStackKb;
+        public int shmemKb;
         public int totalIonKb;
         public int gpuTotalUsageKb;
         public int gpuPrivateAllocationsKb;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 94f483c..2049f3d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2031,11 +2031,14 @@
     @Override
     public void updateMediaTapToTransferReceiverDisplay(
             @StatusBarManager.MediaTransferReceiverState int displayState,
-            MediaRoute2Info routeInfo) {
+            MediaRoute2Info routeInfo,
+            @Nullable Icon appIcon,
+            @Nullable CharSequence appName) {
         enforceMediaContentControl();
         if (mBar != null) {
             try {
-                mBar.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo);
+                mBar.updateMediaTapToTransferReceiverDisplay(
+                        displayState, routeInfo, appIcon, appName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e);
             }
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 27c0bee..10e868d 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -15,6 +15,13 @@
  */
 package com.android.server.tracing;
 
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_HANDOFF;
+import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.content.ComponentName;
@@ -39,6 +46,7 @@
 import android.util.Slog;
 
 import com.android.internal.infra.ServiceConnector;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.SystemService;
 
 import java.io.IOException;
@@ -71,6 +79,17 @@
     private static final String INTENT_ACTION_NOTIFY_SESSION_STOLEN =
             "com.android.traceur.NOTIFY_SESSION_STOLEN";
 
+    private static final int REPORT_BEGIN =
+            TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN;
+    private static final int REPORT_SVC_HANDOFF =
+            TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_HANDOFF;
+    private static final int REPORT_BIND_PERM_INCORRECT =
+            TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT;
+    private static final int REPORT_SVC_PERM_MISSING =
+            TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING;
+    private static final int REPORT_SVC_COMM_ERROR =
+            TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR;
+
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices;
@@ -134,17 +153,24 @@
     }
 
     private void reportTrace(@NonNull TraceReportParams params) {
+        FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_BEGIN,
+                params.uuidLsb, params.uuidMsb);
+
         // We don't need to do any permission checks on the caller because access
         // to this service is guarded by SELinux.
         ComponentName component = new ComponentName(params.reporterPackageName,
                 params.reporterClassName);
         if (!hasBindServicePermission(component)) {
+            FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_BIND_PERM_INCORRECT,
+                    params.uuidLsb, params.uuidMsb);
             return;
         }
         boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP);
         boolean hasUsageStatsPermission = hasPermission(component,
                 Manifest.permission.PACKAGE_USAGE_STATS);
         if (!hasDumpPermission || !hasUsageStatsPermission) {
+            FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_PERM_MISSING,
+                    params.uuidLsb, params.uuidMsb);
             return;
         }
         final long ident = Binder.clearCallingIdentity();
@@ -178,8 +204,13 @@
             message.what = TraceReportService.MSG_REPORT_TRACE;
             message.obj = params;
             messenger.send(message);
+
+            FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_HANDOFF,
+                    params.uuidLsb, params.uuidMsb);
         }).whenComplete((res, err) -> {
             if (err != null) {
+                FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_COMM_ERROR,
+                        params.uuidLsb, params.uuidMsb);
                 Slog.e(TAG, "Failed to report trace", err);
             }
             try {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index f15d2bb..e02fabd 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2543,6 +2543,7 @@
 
         @Override
         public int getClientPriority(int useCase, String sessionId) {
+            ensureTunerResourceAccessPermission();
             final int callingPid = Binder.getCallingPid();
             final long identity = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 56985af..87c8a79 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -429,7 +429,8 @@
         if (needsExtraction) {
             extractColors(wallpaper);
         }
-        notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId, displayId);
+        notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which,
+                wallpaper.userId, displayId);
     }
 
     private static <T extends IInterface> boolean emptyCallbackList(RemoteCallbackList<T> list) {
@@ -1519,7 +1520,6 @@
                 if (mWallpaper.mWallpaperDimAmount != 0f) {
                     try {
                         connector.mEngine.applyDimming(mWallpaper.mWallpaperDimAmount);
-                        notifyWallpaperColorsChanged(mWallpaper, FLAG_SYSTEM);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to dim wallpaper", e);
                     }
@@ -2711,8 +2711,31 @@
             extractColors(wallpaperData);
         }
 
+        return getAdjustedWallpaperColorsOnDimming(wallpaperData);
+    }
+
+    /**
+     * Gets the adjusted {@link WallpaperColors} if the wallpaper colors were not extracted from
+     * bitmap (i.e. it's a live wallpaper) and the dim amount is not 0. If these conditions apply,
+     * default to using color hints that do not support dark theme and dark text.
+     *
+     * @param wallpaperData WallpaperData containing the WallpaperColors and mWallpaperDimAmount
+     */
+    WallpaperColors getAdjustedWallpaperColorsOnDimming(WallpaperData wallpaperData) {
         synchronized (mLock) {
-            return wallpaperData.primaryColors;
+            WallpaperColors wallpaperColors = wallpaperData.primaryColors;
+
+            if (wallpaperColors != null
+                    && (wallpaperColors.getColorHints() & WallpaperColors.HINT_FROM_BITMAP) == 0
+                    && wallpaperData.mWallpaperDimAmount != 0f) {
+                int adjustedColorHints = wallpaperColors.getColorHints()
+                        & ~WallpaperColors.HINT_SUPPORTS_DARK_TEXT
+                        & ~WallpaperColors.HINT_SUPPORTS_DARK_THEME;
+                return new WallpaperColors(
+                        wallpaperColors.getPrimaryColor(), wallpaperColors.getSecondaryColor(),
+                        wallpaperColors.getTertiaryColor(), adjustedColorHints);
+            }
+            return wallpaperColors;
         }
     }
 
@@ -2782,6 +2805,7 @@
                     wallpaper.fromForegroundApp = fromForegroundApp;
                     wallpaper.cropHint.set(cropHint);
                     wallpaper.allowBackup = allowBackup;
+                    wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
                 }
                 return pfd;
             } finally {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ec4c58f..d526845 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1088,7 +1088,7 @@
             final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
             if (r != null && r.isState(RESUMED, PAUSING)) {
                 r.mDisplayContent.mAppTransition.overridePendingAppTransition(
-                        packageName, enterAnim, exitAnim, null, null,
+                        packageName, enterAnim, exitAnim, backgroundColor, null, null,
                         r.mOverrideTaskTransition);
                 r.mTransitionController.setOverrideAnimation(
                         TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9532a5c..6ef6c7a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -811,6 +811,27 @@
     // How long we wait until giving up transfer splash screen.
     private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000;
 
+    /**
+     * The icon is shown when the launching activity sets the splashScreenStyle to
+     * SPLASH_SCREEN_STYLE_ICON. If the launching activity does not specify any style,
+     * follow the system behavior.
+     *
+     * @see android.R.attr#windowSplashScreenBehavior
+     */
+    private static final int SPLASH_SCREEN_BEHAVIOR_DEFAULT = 0;
+    /**
+     * The icon is shown unless the launching app specified SPLASH_SCREEN_STYLE_EMPTY.
+     *
+     * @see android.R.attr#windowSplashScreenBehavior
+     */
+    private static final int SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED = 1;
+
+    @IntDef(prefix = {"SPLASH_SCREEN_BEHAVIOR_"}, value = {
+            SPLASH_SCREEN_BEHAVIOR_DEFAULT,
+            SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED
+    })
+    @interface SplashScreenBehavior { }
+
     // TODO: Have a WindowContainer state for tracking exiting/deferred removal.
     boolean mIsExiting;
     // Force an app transition to be ran in the case the visibility of the app did not change.
@@ -2483,10 +2504,20 @@
     }
 
     void removeStartingWindow() {
+        boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
+
         if (transferSplashScreenIfNeeded()) {
             return;
         }
         removeStartingWindowAnimation(true /* prepareAnimation */);
+
+        // TODO(b/215316431): Add tests
+        final Task task = getTask();
+        if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
+                && task != null) {
+            // Trigger TaskInfoChanged to update the letterbox education.
+            task.dispatchTaskInfoChangedIfNeeded(true /* force */);
+        }
     }
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
@@ -4485,6 +4516,7 @@
                         pendingOptions.getPackageName(),
                         pendingOptions.getCustomEnterResId(),
                         pendingOptions.getCustomExitResId(),
+                        pendingOptions.getCustomBackgroundColor(),
                         pendingOptions.getAnimationStartedListener(),
                         pendingOptions.getAnimationFinishedListener(),
                         pendingOptions.getOverrideTaskTransition());
@@ -6594,8 +6626,23 @@
         return null;
     }
 
+    private boolean isIconStylePreferred(int theme) {
+        if (theme == 0) {
+            return false;
+        }
+        final AttributeCache.Entry ent = AttributeCache.instance().get(packageName, theme,
+                R.styleable.Window, mWmService.mCurrentUserId);
+        if (ent != null) {
+            if (ent.array.hasValue(R.styleable.Window_windowSplashScreenBehavior)) {
+                return ent.array.getInt(R.styleable.Window_windowSplashScreenBehavior,
+                        SPLASH_SCREEN_BEHAVIOR_DEFAULT)
+                        == SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED;
+            }
+        }
+        return false;
+    }
     private boolean shouldUseEmptySplashScreen(ActivityRecord sourceRecord, boolean startActivity,
-            ActivityOptions options) {
+            ActivityOptions options, int resolvedTheme) {
         if (sourceRecord == null && !startActivity) {
             // Use empty style if this activity is not top activity. This could happen when adding
             // a splash screen window to the warm start activity which is re-create because top is
@@ -6605,11 +6652,14 @@
                 return true;
             }
         }
+
+        // setSplashScreenStyle decide in priority of windowSplashScreenBehavior.
         if (options != null) {
             final int optionsStyle = options.getSplashScreenStyle();
             if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_EMPTY) {
                 return true;
-            } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) {
+            } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON
+                    || isIconStylePreferred(resolvedTheme)) {
                 return false;
             }
             // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is
@@ -6619,6 +6669,8 @@
             } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
                 return true;
             }
+        } else if (isIconStylePreferred(resolvedTheme)) {
+            return false;
         }
         if (sourceRecord == null) {
             sourceRecord = searchCandidateLaunchingActivity();
@@ -6691,13 +6743,13 @@
             return;
         }
 
-        mSplashScreenStyleEmpty = shouldUseEmptySplashScreen(
-                sourceRecord, startActivity, startOptions);
-
         final int splashScreenTheme = startActivity ? getSplashscreenTheme(startOptions) : 0;
         final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,
                 splashScreenTheme);
 
+        mSplashScreenStyleEmpty = shouldUseEmptySplashScreen(sourceRecord, startActivity,
+                startOptions, resolvedTheme);
+
         final boolean activityCreated =
                 mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal();
         // If this activity is just created and all activities below are finish, treat this
@@ -7660,6 +7712,8 @@
      *     <li>The activity is eligible for fixed orientation letterbox.
      *     <li>The activity is in fullscreen.
      *     <li>The activity is portrait-only.
+     *     <li>The activity doesn't have a starting window (education should only be displayed
+     *     once the starting window is removed in {@link #removeStartingWindow}).
      * </ul>
      */
     // TODO(b/215316431): Add tests
@@ -7667,7 +7721,8 @@
         return mWmService.mLetterboxConfiguration.getIsEducationEnabled()
                 && mIsEligibleForFixedOrientationLetterbox
                 && getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+                && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
+                && mStartingWindow == null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5164bf0..fb3d17a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2003,8 +2003,8 @@
      * @param targetTask the target task for launching activity, which could be different from
      *                   the one who hosting the embedding.
      */
-    private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, ActivityRecord starting,
-            boolean newTask, Task targetTask) {
+    private boolean canEmbedActivity(@NonNull TaskFragment taskFragment,
+            @NonNull ActivityRecord starting, boolean newTask, Task targetTask) {
         final Task hostTask = taskFragment.getTask();
         if (hostTask == null) {
             return false;
@@ -2016,8 +2016,7 @@
             return true;
         }
 
-        // Not allowed embedding an activity of another app.
-        if (hostUid != starting.getUid()) {
+        if (!taskFragment.isAllowedToEmbedActivity(starting)) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ca4d717..580ab179 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5267,6 +5267,18 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the process represented by the pid passed as argument is
+     * instrumented.
+     */
+    boolean isInstrumenting(int pid) {
+        final WindowProcessController process;
+        synchronized (mGlobalLock) {
+            process = mProcessMap.getProcess(pid);
+        }
+        return process != null ? process.isInstrumenting() : false;
+    }
+
     final class H extends Handler {
         static final int REPORT_TIME_TRACKER_MSG = 1;
         static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 5899a4e..a743091 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -42,6 +42,22 @@
     boolean getShowWallpaper();
 
     /**
+     * @return Whether we should show a background behind the animating windows.
+     * @see Animation#getShowBackground()
+     */
+    default boolean getShowBackground() {
+        return false;
+    }
+
+    /**
+     * @return The background color to use during an animation if getShowBackground returns true.
+     * @see Animation#getBackgroundColor()
+     */
+    default int getBackgroundColor() {
+        return 0;
+    }
+
+    /**
      * Requests to start the animation.
      *
      * @param animationLeash The surface to run the animation on. See {@link SurfaceAnimator} for an
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 56adcfd..05efb29 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -93,6 +93,7 @@
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
 
+import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -198,6 +199,7 @@
     private IRemoteCallback mAnimationFinishedCallback;
     private int mNextAppTransitionEnter;
     private int mNextAppTransitionExit;
+    private @ColorInt int mNextAppTransitionBackgroundColor;
     private int mNextAppTransitionInPlace;
     private boolean mNextAppTransitionIsSync;
 
@@ -829,6 +831,7 @@
                     break;
             }
             a = animAttr != 0 ? loadAnimationAttr(lp, animAttr, transit) : null;
+
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b "
                             + "Callers=%s",
@@ -836,6 +839,11 @@
                     Debug.getCallers(3));
         }
         setAppTransitionFinishedCallbackIfNeeded(a);
+
+        if (mNextAppTransitionBackgroundColor != 0) {
+            a.setBackgroundColor(mNextAppTransitionBackgroundColor);
+        }
+
         return a;
     }
 
@@ -861,14 +869,15 @@
     }
 
     void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
-            IRemoteCallback startedCallback, IRemoteCallback endedCallback,
-            boolean overrideTaskTransaction) {
+            @ColorInt int backgroundColor, IRemoteCallback startedCallback,
+            IRemoteCallback endedCallback, boolean overrideTaskTransaction) {
         if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionOverrideRequested = true;
             mNextAppTransitionPackage = packageName;
             mNextAppTransitionEnter = enterAnim;
             mNextAppTransitionExit = exitAnim;
+            mNextAppTransitionBackgroundColor = backgroundColor;
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
             mAnimationFinishedCallback = endedCallback;
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 5a2cf17..afa4f19 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -544,6 +544,11 @@
         return getActivityType() == ACTIVITY_TYPE_RECENTS;
     }
 
+    final boolean isActivityTypeHomeOrRecents() {
+        final int activityType = getActivityType();
+        return activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
+    }
+
     public boolean isActivityTypeAssistant() {
         return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 092cff3..e845034 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1054,6 +1054,7 @@
         mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
         mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
         mAppTransitionController = new AppTransitionController(mWmService, this);
+        mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
         mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
 
         final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
@@ -1111,6 +1112,7 @@
         t.remove(mSurfaceControl);
 
         mLastSurfacePosition.set(0, 0);
+        mLastDeltaRotation = Surface.ROTATION_0;
 
         configureSurfaces(t);
 
@@ -1604,7 +1606,7 @@
      */
     @Rotation
     int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) {
-        if (mTransitionController.isShellTransitionsEnabled()) {
+        if (mTransitionController.useShellTransitionsRotation()) {
             return ROTATION_UNDEFINED;
         }
         if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
@@ -1645,18 +1647,30 @@
             // It has been set and not yet finished.
             return true;
         }
-        if (!r.occludesParent() || r.isVisible()) {
+        if (!r.occludesParent()) {
             // While entering or leaving a translucent or floating activity (e.g. dialog style),
             // there is a visible activity in the background. Then it still needs rotation animation
             // to cover the activity configuration change.
             return false;
         }
+        if (mTransitionController.isShellTransitionsEnabled()
+                ? mTransitionController.wasVisibleAtStart(r) : r.isVisible()) {
+            // If activity is already visible, then it's not "launching". However, shell-transitions
+            // will make it visible immediately.
+            return false;
+        }
         if (checkOpening) {
-            if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
-                // Apply normal rotation animation in case of the activity set different requested
-                // orientation without activity switch, or the transition is unset due to starting
-                // window was transferred ({@link #mSkipAppTransitionAnimation}).
-                return false;
+            if (mTransitionController.isShellTransitionsEnabled()) {
+                if (!mTransitionController.isCollecting(r)) {
+                    return false;
+                }
+            } else {
+                if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
+                    // Apply normal rotation animation in case of the activity set different
+                    // requested orientation without activity switch, or the transition is unset due
+                    // to starting window was transferred ({@link #mSkipAppTransitionAnimation}).
+                    return false;
+                }
             }
             if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) {
                 // If the activity is executing or has done the lifecycle callback, use normal
@@ -1733,15 +1747,19 @@
     }
 
     void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
+        final boolean useAsyncRotation = !mTransitionController.isShellTransitionsEnabled();
         if (mFixedRotationLaunchingApp == null && r != null) {
-            mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
-            startAsyncRotation(
-                    // Delay the hide animation to avoid blinking by clicking navigation bar that
-                    // may toggle fixed rotation in a short time.
-                    r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
+            mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this,
+                    rotation);
+            if (useAsyncRotation) {
+                startAsyncRotation(
+                        // Delay the hide animation to avoid blinking by clicking navigation bar
+                        // that may toggle fixed rotation in a short time.
+                        r == mFixedRotationTransitionListener.mAnimatingRecents);
+            }
         } else if (mFixedRotationLaunchingApp != null && r == null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
-            finishAsyncRotationIfPossible();
+            if (useAsyncRotation) finishAsyncRotationIfPossible();
         }
         mFixedRotationLaunchingApp = r;
     }
@@ -1760,7 +1778,8 @@
         if (prevRotatedLaunchingApp != null
                 && prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation
                 // It is animating so we can expect there will have a transition callback.
-                && prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)) {
+                && (prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)
+                        || mTransitionController.inTransition(prevRotatedLaunchingApp))) {
             // It may be the case that multiple activities launch consecutively. Because their
             // rotation are the same, the transformed state can be shared to avoid duplicating
             // the heavy operations. This also benefits that the states of multiple activities
@@ -1798,6 +1817,7 @@
         }
         // Update directly because the app which will change the orientation of display is ready.
         if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
+            mTransitionController.setSeamlessRotation(this);
             sendNewConfiguration();
             return;
         }
@@ -3107,12 +3127,16 @@
         mDisplayPolicy.switchUser();
     }
 
-    @Override
-    void removeIfPossible() {
-        if (isAnimating(TRANSITION | PARENTS)
+    private boolean shouldDeferRemoval() {
+        return isAnimating(TRANSITION | PARENTS)
                 // isAnimating is a legacy transition query and will be removed, so also add a
                 // check for whether this is in a shell-transition when not using legacy.
-                || mTransitionController.inTransition()) {
+                || mTransitionController.isTransitionOnDisplay(this);
+    }
+
+    @Override
+    void removeIfPossible() {
+        if (shouldDeferRemoval()) {
             mDeferredRemoval = true;
             return;
         }
@@ -3129,6 +3153,7 @@
             mChangingContainers.clear();
             mUnknownAppVisibilityController.clear();
             mAppTransition.removeAppTransitionTimeoutCallbacks();
+            mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
             handleAnimatingStoppedAndTransition();
             mWmService.stopFreezingDisplayLocked();
             super.removeImmediately();
@@ -3140,6 +3165,8 @@
             mInputMonitor.onDisplayRemoved();
             mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
             mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
+            mRootWindowContainer.mTaskSupervisor
+                    .getKeyguardController().onDisplayRemoved(mDisplayId);
         } finally {
             mDisplayReady = false;
         }
@@ -3153,7 +3180,8 @@
     /** Returns true if a removal action is still being deferred. */
     @Override
     boolean handleCompleteDeferredRemoval() {
-        final boolean stillDeferringRemoval = super.handleCompleteDeferredRemoval();
+        final boolean stillDeferringRemoval =
+                super.handleCompleteDeferredRemoval() || shouldDeferRemoval();
 
         if (!stillDeferringRemoval && mDeferredRemoval) {
             removeImmediately();
@@ -4062,7 +4090,7 @@
             final boolean renewImeSurface = mImeScreenshot == null
                     || mImeScreenshot.getWidth() != mInputMethodWindow.getFrame().width()
                     || mImeScreenshot.getHeight() != mInputMethodWindow.getFrame().height();
-            if (task != null && !task.isHomeOrRecentsRootTask()) {
+            if (task != null && !task.isActivityTypeHomeOrRecents()) {
                 SurfaceControl.ScreenshotHardwareBuffer imeBuffer = renewImeSurface
                         ? mWmService.mTaskSnapshotController.snapshotImeFromAttachedTask(task)
                         : null;
@@ -5761,7 +5789,10 @@
         mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration);
         super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
         mCurrentOverrideConfigurationChanges = 0;
-        mWmService.setNewDisplayOverrideConfiguration(currOverrideConfig, this);
+        if (mWaitingForConfig) {
+            mWaitingForConfig = false;
+            mWmService.mLastFinishedFreezeSource = "new-config";
+        }
         mAtmService.addWindowLayoutReasons(
                 ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
     }
@@ -5891,8 +5922,6 @@
             forAllRootTasks(t -> {t.removeIfPossible("releaseSelfIfNeeded");});
         } else if (getTopRootTask() == null) {
             removeIfPossible();
-            mRootWindowContainer.mTaskSupervisor
-                    .getKeyguardController().onDisplayRemoved(mDisplayId);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 2a05d05..6438d79 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.RotationUtils.deltaRotation;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
@@ -39,7 +40,6 @@
 
 import android.annotation.AnimRes;
 import android.annotation.IntDef;
-import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -50,7 +50,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.power.Boost;
-import android.net.Uri;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -58,6 +57,7 @@
 import android.provider.Settings;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.IDisplayWindowRotationCallback;
 import android.view.IWindowManager;
@@ -77,6 +77,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayDeque;
 
 /**
  * Defines the mapping between orientation and rotation of a display.
@@ -106,6 +107,7 @@
     private final int mDeskDockRotation;
     private final int mUndockedHdmiRotation;
     private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
+    private final RotationHistory mRotationHistory = new RotationHistory();
 
     private OrientationListener mOrientationListener;
     private StatusBarManagerInternal mStatusBarManagerInternal;
@@ -139,6 +141,8 @@
     @VisibleForTesting
     int mUpsideDownRotation; // "other" portrait
 
+    int mLastSensorRotation = -1;
+
     private boolean mAllowSeamlessRotationDespiteNavBarMoving;
 
     private int mDeferredRotationPauseCount;
@@ -351,6 +355,7 @@
     }
 
     void applyCurrentRotation(@Surface.Rotation int rotation) {
+        mRotationHistory.addRecord(this, rotation);
         if (mOrientationListener != null) {
             mOrientationListener.setCurrentRotation(rotation);
         }
@@ -1149,6 +1154,7 @@
         int sensorRotation = mOrientationListener != null
                 ? mOrientationListener.getProposedRotation() // may be -1
                 : -1;
+        mLastSensorRotation = sensorRotation;
         if (sensorRotation < 0) {
             sensorRotation = lastRotation;
         }
@@ -1537,6 +1543,15 @@
         pw.println(" mUndockedHdmiRotation=" + Surface.rotationToString(mUndockedHdmiRotation));
         pw.println(prefix + "  mLidOpenRotation=" + Surface.rotationToString(mLidOpenRotation));
         pw.println(prefix + "  mFixedToUserRotation=" + isFixedToUserRotation());
+
+        if (!mRotationHistory.mRecords.isEmpty()) {
+            pw.println();
+            pw.println(prefix + "  RotationHistory");
+            prefix = "    " + prefix;
+            for (RotationHistory.Record r : mRotationHistory.mRecords) {
+                r.dump(prefix, pw);
+            }
+        }
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -1650,9 +1665,69 @@
         }
     }
 
-    @VisibleForTesting
-    interface ContentObserverRegister {
-        void registerContentObserver(Uri uri, boolean notifyForDescendants,
-                ContentObserver observer, @UserIdInt int userHandle);
+    private static class RotationHistory {
+        private static final int MAX_SIZE = 8;
+        private static class Record {
+            final @Surface.Rotation int mFromRotation;
+            final @Surface.Rotation int mToRotation;
+            final @Surface.Rotation int mUserRotation;
+            final @WindowManagerPolicy.UserRotationMode int mUserRotationMode;
+            final int mSensorRotation;
+            final boolean mIgnoreOrientationRequest;
+            final String mNonDefaultRequestingTaskDisplayArea;
+            final String mLastOrientationSource;
+            final @ActivityInfo.ScreenOrientation int mSourceOrientation;
+            final long mTimestamp = System.currentTimeMillis();
+
+            Record(DisplayRotation dr, int fromRotation, int toRotation) {
+                mFromRotation = fromRotation;
+                mToRotation = toRotation;
+                mUserRotation = dr.mUserRotation;
+                mUserRotationMode = dr.mUserRotationMode;
+                final OrientationListener listener = dr.mOrientationListener;
+                mSensorRotation = (listener == null || !listener.mEnabled)
+                        ? -2 /* disabled */ : dr.mLastSensorRotation;
+                final DisplayContent dc = dr.mDisplayContent;
+                mIgnoreOrientationRequest = dc.mIgnoreOrientationRequest;
+                final TaskDisplayArea requestingTda = dc.getOrientationRequestingTaskDisplayArea();
+                mNonDefaultRequestingTaskDisplayArea = requestingTda == null
+                        ? "none" : requestingTda != dc.getDefaultTaskDisplayArea()
+                        ? requestingTda.toString() : null;
+                final WindowContainer<?> source = dc.getLastOrientationSource();
+                if (source != null) {
+                    mLastOrientationSource = source.toString();
+                    mSourceOrientation = source.mOrientation;
+                } else {
+                    mLastOrientationSource = null;
+                    mSourceOrientation = SCREEN_ORIENTATION_UNSET;
+                }
+            }
+
+            void dump(String prefix, PrintWriter pw) {
+                pw.println(prefix + TimeUtils.logTimeOfDay(mTimestamp)
+                        + " " + Surface.rotationToString(mFromRotation)
+                        + " to " + Surface.rotationToString(mToRotation));
+                pw.println(prefix + "  source=" + mLastOrientationSource
+                        + " " + ActivityInfo.screenOrientationToString(mSourceOrientation));
+                pw.println(prefix + "  mode="
+                        + WindowManagerPolicy.userRotationModeToString(mUserRotationMode)
+                        + " user=" + Surface.rotationToString(mUserRotation)
+                        + " sensor=" + Surface.rotationToString(mSensorRotation));
+                if (mIgnoreOrientationRequest) pw.println(prefix + "  ignoreRequest=true");
+                if (mNonDefaultRequestingTaskDisplayArea != null) {
+                    pw.println(prefix + "  requestingTda=" + mNonDefaultRequestingTaskDisplayArea);
+                }
+            }
+        }
+
+        final ArrayDeque<Record> mRecords = new ArrayDeque<>(MAX_SIZE);
+
+        void addRecord(DisplayRotation dr, int toRotation) {
+            if (mRecords.size() >= MAX_SIZE) {
+                mRecords.removeFirst();
+            }
+            final int fromRotation = dr.mDisplayContent.getWindowConfiguration().getRotation();
+            mRecords.addLast(new Record(dr, fromRotation, toRotation));
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 8db4306..2ab08e6 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -156,6 +156,7 @@
         final IWindow mClient;
         @Nullable final WindowState mHostWindowState;
         @Nullable final ActivityRecord mHostActivityRecord;
+        final String mName;
         final int mOwnerUid;
         final int mOwnerPid;
         final WindowManagerService mWmService;
@@ -186,7 +187,7 @@
          */
         EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken,
                        WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
-                       int displayId, IBinder focusGrantToken) {
+                       int displayId, IBinder focusGrantToken, String inputHandleName) {
             mSession = session;
             mWmService = service;
             mClient = clientToken;
@@ -198,14 +199,15 @@
             mWindowType = windowType;
             mDisplayId = displayId;
             mFocusGrantToken = focusGrantToken;
+            final String hostWindowName =
+                    (mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString()
+                            : "";
+            mName = "Embedded{" + inputHandleName + hostWindowName + "}";
         }
 
         @Override
         public String toString() {
-            final String hostWindowName = (mHostWindowState != null)
-                    ? mHostWindowState.getWindowTag().toString() : "Internal";
-            return "EmbeddedWindow{ u" + UserHandle.getUserId(mOwnerUid) + " " + hostWindowName
-                    + "}";
+            return mName;
         }
 
         InputApplicationHandle getApplicationHandle() {
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 8d3e071..aa5e20d 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -104,7 +104,8 @@
         for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer child = mTaskFragment.mChildren.get(i);
             final TaskFragment childTaskFragment = child.asTaskFragment();
-            if (childTaskFragment != null && childTaskFragment.topRunningActivity() != null) {
+            if (childTaskFragment != null
+                    && childTaskFragment.getTopNonFinishingActivity() != null) {
                 childTaskFragment.updateActivityVisibilities(starting, configChanges,
                         preserveWindows, notifyClients);
                 mBehindFullyOccludedContainer |=
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 2f95119..d28dfd5 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -144,15 +144,21 @@
                 getStatusControlTarget(focusedWin, false /* fake */);
         final InsetsControlTarget navControlTarget =
                 getNavControlTarget(focusedWin, false /* fake */);
+        final WindowState notificationShade = mPolicy.getNotificationShade();
+        final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
         mStateController.onBarControlTargetChanged(
                 statusControlTarget,
                 statusControlTarget == mDummyControlTarget
                         ? getStatusControlTarget(focusedWin, true /* fake */)
-                        : null,
+                        : statusControlTarget == notificationShade
+                                ? getStatusControlTarget(topApp, true /* fake */)
+                                : null,
                 navControlTarget,
                 navControlTarget == mDummyControlTarget
                         ? getNavControlTarget(focusedWin, true /* fake */)
-                        : null);
+                        : navControlTarget == notificationShade
+                                ? getNavControlTarget(topApp, true /* fake */)
+                                : null);
         mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);
         mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);
     }
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index a3eb980..61f9fe2 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -51,6 +51,16 @@
     }
 
     @Override
+    public boolean getShowBackground() {
+        return mSpec.getShowBackground();
+    }
+
+    @Override
+    public int getBackgroundColor() {
+        return mSpec.getBackgroundColor();
+    }
+
+    @Override
     public void startAnimation(SurfaceControl animationLeash, Transaction t,
             @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
         mAnimator.startAnimation(mSpec, animationLeash, t,
@@ -97,6 +107,20 @@
         }
 
         /**
+         * @see AnimationAdapter#getShowBackground
+         */
+        default boolean getShowBackground() {
+            return false;
+        }
+
+        /**
+         * @see AnimationAdapter#getBackgroundColor
+         */
+        default int getBackgroundColor() {
+            return 0;
+        }
+
+        /**
          * @see AnimationAdapter#getStatusBarTransitionsStartTime
          */
         default long calculateStatusBarTransitionStartTime() {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index d4a7a5d..e259238 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -529,7 +529,7 @@
      */
     void notifyTaskPersisterLocked(Task task, boolean flush) {
         final Task rootTask = task != null ? task.getRootTask() : null;
-        if (rootTask != null && rootTask.isHomeOrRecentsRootTask()) {
+        if (rootTask != null && rootTask.isActivityTypeHomeOrRecents()) {
             // Never persist the home or recents task.
             return;
         }
@@ -563,7 +563,7 @@
 
     private static boolean shouldPersistTaskLocked(Task task) {
         final Task rootTask = task.getRootTask();
-        return task.isPersistable && (rootTask == null || !rootTask.isHomeOrRecentsRootTask());
+        return task.isPersistable && (rootTask == null || !rootTask.isActivityTypeHomeOrRecents());
     }
 
     void onSystemReadyLocked() {
@@ -994,7 +994,7 @@
             }
             final Task rootTask = task.getRootTask();
             if ((task.isPersistable || task.inRecents)
-                    && (rootTask == null || !rootTask.isHomeOrRecentsRootTask())) {
+                    && (rootTask == null || !rootTask.isActivityTypeHomeOrRecents())) {
                 if (TaskPersister.DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
                 persistentTaskIds.add(task.mTaskId);
             } else {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 1183094..30906e5 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -18,8 +18,7 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
@@ -129,7 +128,6 @@
     private ActivityRecord mTargetActivityRecord;
     private DisplayContent mDisplayContent;
     private int mTargetActivityType;
-    private Rect mMinimizedHomeBounds = new Rect();
 
     // We start the RecentsAnimationController in a pending-start state since we need to wait for
     // the wallpaper/activity to draw before we can give control to the handler to start animating
@@ -480,11 +478,6 @@
             mDisplayContent.setLayoutNeeded();
         }
 
-        // Save the minimized home height
-        final Task rootHomeTask =
-                mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
-        mMinimizedHomeBounds = rootHomeTask != null ? rootHomeTask.getBounds() : null;
-
         mService.mWindowPlacerLocked.performSurfacePlacement();
 
         mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity);
@@ -502,9 +495,7 @@
      */
     private boolean skipAnimation(Task task) {
         final WindowConfiguration config = task.getWindowConfiguration();
-        return task.isAlwaysOnTop()
-                || config.tasksAreFloating()
-                || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+        return task.isAlwaysOnTop() || config.tasksAreFloating();
     }
 
     @VisibleForTesting
@@ -568,10 +559,6 @@
             // insets for the target app window after a rotation
             mDisplayContent.performLayout(false /* initial */, false /* updateInputWindows */);
 
-            final Rect minimizedHomeBounds = mTargetActivityRecord != null
-                    && mTargetActivityRecord.inSplitScreenSecondaryWindowingMode()
-                            ? mMinimizedHomeBounds
-                            : null;
             final Rect contentInsets;
             final WindowState targetAppMainWindow = getTargetAppMainWindow();
             if (targetAppMainWindow != null) {
@@ -585,7 +572,7 @@
                 contentInsets = mTmpRect;
             }
             mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets,
-                    minimizedHomeBounds);
+                    null);
             ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
                     "startAnimation(): Notify animation start: %s",
                     mPendingAnimations.stream()
@@ -623,16 +610,17 @@
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
             final Task task = adapter.mTask;
-            final boolean isSplitScreenSecondary =
-                    task.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-            if (task.isHomeOrRecentsRootTask()
-                    // TODO(b/178449492): Will need to update for the new split screen mode once
-                    // it's ready.
-                    // Skip if the task is the secondary split screen and in landscape.
-                    || (isSplitScreenSecondary && isDisplayLandscape)) {
+            final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment();
+            final boolean inSplitScreen = task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+                    && adjacentTask != null;
+            if (task.isActivityTypeHomeOrRecents()
+                    // Skip if the task is in split screen and in landscape.
+                    || (inSplitScreen && isDisplayLandscape)
+                    // Skip if the task is the top task in split screen.
+                    || (inSplitScreen && task.getBounds().top < adjacentTask.getBounds().top)) {
                 continue;
             }
-            shouldTranslateNavBar = isSplitScreenSecondary;
+            shouldTranslateNavBar = inSplitScreen;
             mNavBarAttachedApp = task.getTopVisibleActivity();
             break;
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 76a7981..d535018 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -637,36 +637,6 @@
         return null;
     }
 
-    /**
-     * Set new display override config. If called for the default display, global configuration
-     * will also be updated.
-     */
-    void setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration,
-            @NonNull DisplayContent displayContent) {
-
-        final Configuration currentConfig = displayContent.getRequestedOverrideConfiguration();
-        final boolean configChanged = currentConfig.diff(newConfiguration) != 0;
-        if (!configChanged) {
-            return;
-        }
-
-        displayContent.onRequestedOverrideConfigurationChanged(newConfiguration);
-
-        if (displayContent.getDisplayId() == DEFAULT_DISPLAY) {
-            // Override configuration of the default display duplicates global config. In this case
-            // we also want to update the global config.
-            setGlobalConfigurationIfNeeded(newConfiguration);
-        }
-    }
-
-    private void setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
-        final boolean configChanged = getConfiguration().diff(newConfiguration) != 0;
-        if (!configChanged) {
-            return;
-        }
-        onConfigurationChanged(newConfiguration);
-    }
-
     @Override
     void dispatchConfigurationToChild(DisplayContent child, Configuration config) {
         if (child.isDefaultDisplay) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7c5144e..de8ea8c 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -837,7 +837,7 @@
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface,
             IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
-            IBinder focusGrantToken, InputChannel outInputChannel) {
+            IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) {
         if (hostInputToken == null && !mCanAddInternalSystemWindow) {
             // Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
             // embedded windows without providing a host window input token
@@ -853,7 +853,8 @@
         try {
             mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
                     flags, mCanAddInternalSystemWindow ? privateFlags : 0,
-                    mCanAddInternalSystemWindow ? type : 0, focusGrantToken, outInputChannel);
+                    mCanAddInternalSystemWindow ? type : 0, focusGrantToken, inputHandleName,
+                    outInputChannel);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b84ef77..6df54cd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2711,7 +2711,7 @@
         // they extend past their root task and sysui uses the root task surface to control
         // cropping.
         // TODO(b/158242495): get rid of this when drag/drop can use surface bounds.
-        if (isActivityTypeHome() || isActivityTypeRecents()) {
+        if (isActivityTypeHomeOrRecents()) {
             // Make sure this is the top-most non-organizer root task (if not top-most, it means
             // another translucent task could be above this, so this needs to stay cropped.
             final Task rootTask = getRootTask();
@@ -3306,7 +3306,7 @@
         if (control != null) {
             // We let the transition to be controlled by RecentsAnimation, and callback task's
             // RemoteAnimationTarget for remote runner to animate.
-            if (enter && !isHomeOrRecentsRootTask()) {
+            if (enter && !isActivityTypeHomeOrRecents()) {
                 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
                         "applyAnimationUnchecked, control: %s, task: %s, transit: %s",
                         control, asTask(), AppTransition.appTransitionOldToString(transit));
@@ -4591,10 +4591,6 @@
                 !PRESERVE_WINDOWS);
     }
 
-    final boolean isHomeOrRecentsRootTask() {
-        return isActivityTypeHome() || isActivityTypeRecents();
-    }
-
     final boolean isOnHomeDisplay() {
         return getDisplayId() == DEFAULT_DISPLAY;
     }
@@ -5044,7 +5040,7 @@
 
         // The transition animation and starting window are not needed if {@code allowMoveToFront}
         // is false, because the activity won't be visible.
-        if ((!isHomeOrRecentsRootTask() || hasActivity()) && allowMoveToFront) {
+        if ((!isActivityTypeHomeOrRecents() || hasActivity()) && allowMoveToFront) {
             final DisplayContent dc = mDisplayContent;
             if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
                     "Prepare open transition: starting " + r);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 177d2e6..c880aba 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -25,6 +25,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -95,6 +96,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -102,6 +104,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -505,6 +508,56 @@
     }
 
     /**
+     * Checks if the organized task fragment is allowed to have the specified activity, which is
+     * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be
+     * enabled.
+     * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
+     */
+    boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
+        if ((a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING)
+                == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) {
+            return true;
+        }
+
+        return isAllowedToEmbedActivityInTrustedMode(a);
+    }
+
+    /**
+     * Checks if the organized task fragment is allowed to embed activity in fully trusted mode,
+     * which means that all transactions are allowed. This is supported in the following cases:
+     * <li>the activity belongs to the same app as the organizer host;</li>
+     * <li>the activity has declared the organizer host as trusted explicitly via known
+     * certificate.</li>
+     */
+    private boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) {
+        if (mTaskFragmentOrganizerUid == a.getUid()) {
+            // Activities from the same UID can be embedded freely by the host.
+            return true;
+        }
+
+        Set<String> knownActivityEmbeddingCerts = a.info.getKnownActivityEmbeddingCerts();
+        if (knownActivityEmbeddingCerts.isEmpty()) {
+            // An application must either declare that it allows untrusted embedding, or specify
+            // a set of app certificates that are allowed to embed it in trusted mode.
+            return false;
+        }
+
+        AndroidPackage hostPackage = mAtmService.getPackageManagerInternalLocked()
+                .getPackage(mTaskFragmentOrganizerUid);
+
+        return hostPackage != null && hostPackage.getSigningDetails().hasAncestorOrSelfWithDigest(
+                knownActivityEmbeddingCerts);
+    }
+
+    /**
+     * Checks if all activities in the task fragment are allowed to be embedded in trusted mode.
+     * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
+     */
+    boolean isAllowedToBeEmbeddedInTrustedMode() {
+        return forAllActivities(this::isAllowedToEmbedActivityInTrustedMode);
+    }
+
+    /**
      * Returns the TaskFragment that is being organized, which could be this or the ascendant
      * TaskFragment.
      */
@@ -945,6 +998,10 @@
             // we still want to check if the visibility of other windows have changed (e.g. bringing
             // a fullscreen window forward to cover another freeform activity.)
             if (taskDisplayArea.inMultiWindowMode()) {
+                if (taskDisplayArea.mDisplayContent != null
+                        && taskDisplayArea.mDisplayContent.mFocusedApp != next) {
+                    taskDisplayArea.mDisplayContent.setFocusedApp(next);
+                }
                 taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
                         false /* preserveWindows */, true /* notifyClients */);
             }
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 123ca88..19f921d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -294,14 +294,28 @@
         }
     }
 
-    /** Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. */
+    /**
+     * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
+     * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
+     */
     @Nullable
     public RemoteAnimationDefinition getRemoteAnimationDefinition(
             ITaskFragmentOrganizer organizer) {
         synchronized (mGlobalLock) {
             final TaskFragmentOrganizerState organizerState =
                     mTaskFragmentOrganizerState.get(organizer.asBinder());
-            return organizerState != null ? organizerState.mRemoteAnimationDefinition : null;
+            if (organizerState == null) {
+                return null;
+            }
+            for (TaskFragment tf : organizerState.mOrganizedTaskFragments) {
+                if (!tf.isAllowedToBeEmbeddedInTrustedMode()) {
+                    // Disable client-driven animations for organizer if at least one of the
+                    // embedded task fragments is not embedding in trusted mode.
+                    // TODO(b/197364677): replace with a stub or Shell-driven one instead of skip?
+                    return null;
+                }
+            }
+            return organizerState.mRemoteAnimationDefinition;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d86382d..9f2188b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -158,8 +158,8 @@
     /** The final animation targets derived from participants after promotion. */
     private ArrayList<WindowContainer> mTargets;
 
-    /** The main display running this transition. */
-    private DisplayContent mTargetDisplay;
+    /** The displays that this transition is running on. */
+    private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
 
     /**
      * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
@@ -209,6 +209,16 @@
         return mTransientLaunches != null && mTransientLaunches.contains(activity);
     }
 
+    boolean isOnDisplay(@NonNull DisplayContent dc) {
+        return mTargetDisplays.contains(dc);
+    }
+
+    void setSeamlessRotation(@NonNull WindowContainer wc) {
+        final ChangeInfo info = mChanges.get(wc);
+        if (info == null) return;
+        info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION;
+    }
+
     @VisibleForTesting
     int getSyncId() {
         return mSyncId;
@@ -274,6 +284,9 @@
             mChanges.put(wc, info);
         }
         mParticipants.add(wc);
+        if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) {
+            mTargetDisplays.add(wc.getDisplayContent());
+        }
         if (info.mShowWallpaper) {
             // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
             final WindowState wallpaper =
@@ -510,15 +523,23 @@
             dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
         }
 
-        final AsyncRotationController asyncRotationController =
-                mTargetDisplay.getAsyncRotationController();
-        if (asyncRotationController != null) {
-            asyncRotationController.onTransitionFinished();
-        }
-        // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
-        // so re-compute in case the IME target is changed after transition.
-        if (mTransientLaunches != null) {
-            mTargetDisplay.computeImeTarget(true /* updateImeTarget */);
+        for (int i = 0; i < mTargetDisplays.size(); ++i) {
+            final DisplayContent dc = mTargetDisplays.get(i);
+            final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
+            if (asyncRotationController != null) {
+                asyncRotationController.onTransitionFinished();
+            }
+            if (mTransientLaunches != null) {
+                // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
+                // so re-compute in case the IME target is changed after transition.
+                for (int t = 0; t < mTransientLaunches.size(); ++t) {
+                    if (mTransientLaunches.valueAt(t).getDisplayContent() == dc) {
+                        dc.computeImeTarget(true /* updateImeTarget */);
+                        break;
+                    }
+                }
+            }
+            dc.handleCompleteDeferredRemoval();
         }
     }
 
@@ -549,19 +570,13 @@
             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
             return;
         }
-        boolean hasWallpaper = false;
-        DisplayContent dc = null;
-        for (int i = mParticipants.size() - 1; i >= 0; --i) {
-            final WindowContainer<?> wc = mParticipants.valueAt(i);
-            if (dc == null && wc.mDisplayContent != null) {
-                dc = wc.mDisplayContent;
-            }
-            if (!hasWallpaper && isWallpaper(wc)) {
-                hasWallpaper = true;
-            }
+        if (mTargetDisplays.isEmpty()) {
+            mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay());
         }
-        if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay();
-        mTargetDisplay = dc;
+        // While there can be multiple DC's involved. For now, we just use the first one as
+        // the "primary" one for most things. Eventually, this will need to change, but, for the
+        // time being, we don't have full cross-display transitions so it isn't a problem.
+        final DisplayContent dc = mTargetDisplays.get(0);
 
         if (mState == STATE_ABORT) {
             mController.abort(this);
@@ -571,8 +586,11 @@
             return;
         }
         // Ensure that wallpaper visibility is updated with the latest wallpaper target.
-        if (hasWallpaper) {
-            dc.mWallpaperController.adjustWallpaperWindows();
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> wc = mParticipants.valueAt(i);
+            if (isWallpaper(wc) && wc.getDisplayContent() != null) {
+                wc.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
+            }
         }
 
         mState = STATE_PLAYING;
@@ -819,7 +837,7 @@
         // the bottom of the screen, so we need to animate it.
         for (int i = 0; i < mTargets.size(); ++i) {
             final Task task = mTargets.get(i).asTask();
-            if (task == null || !task.isHomeOrRecentsRootTask()) continue;
+            if (task == null || !task.isActivityTypeHomeOrRecents()) continue;
             animate = task.isVisibleRequested();
             break;
         }
@@ -1122,6 +1140,15 @@
             // hardware-screen-level surfaces.
             return asDC.getWindowingLayer();
         }
+        if (!wc.mTransitionController.useShellTransitionsRotation()) {
+            final WindowToken asToken = wc.asWindowToken();
+            if (asToken != null) {
+                // WindowTokens can have a fixed-rotation applied to them. In the current
+                // implementation this fact is hidden from the player, so we must create a leash.
+                final SurfaceControl leash = asToken.getOrCreateFixedRotationLeash();
+                if (leash != null) return leash;
+            }
+        }
         return wc.getSurfaceControl();
     }
 
@@ -1224,6 +1251,8 @@
                 final ActivityRecord topMostActivity = task.getTopMostActivity();
                 change.setAllowEnterPip(topMostActivity != null
                         && topMostActivity.checkEnterPictureInPictureAppOpsState());
+            } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
+                change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
             }
             final ActivityRecord activityRecord = target.asActivityRecord();
             if (activityRecord != null) {
@@ -1337,6 +1366,21 @@
 
     @VisibleForTesting
     static class ChangeInfo {
+        private static final int FLAG_NONE = 0;
+
+        /**
+         * When set, the associated WindowContainer has been explicitly requested to be a
+         * seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
+         */
+        private static final int FLAG_SEAMLESS_ROTATION = 1;
+
+        @IntDef(prefix = { "FLAG_" }, value = {
+                FLAG_NONE,
+                FLAG_SEAMLESS_ROTATION
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface Flag {}
+
         // Usually "post" change state.
         WindowContainer mParent;
 
@@ -1350,6 +1394,9 @@
         int mRotation = ROTATION_UNDEFINED;
         @ActivityInfo.Config int mKnownConfigChanges;
 
+        /** These are just extra info. They aren't used for change-detection. */
+        @Flag int mFlags = FLAG_NONE;
+
         ChangeInfo(@NonNull WindowContainer origState) {
             mVisible = origState.isVisibleRequested();
             mWindowingMode = origState.getWindowingMode();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 60307ce..c267cba 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -34,6 +34,7 @@
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -58,6 +59,10 @@
 class TransitionController {
     private static final String TAG = "TransitionController";
 
+    /** Whether to use shell-transitions rotation instead of fixed-rotation. */
+    private static final boolean SHELL_TRANSITIONS_ROTATION =
+            SystemProperties.getBoolean("persist.debug.shell_transit_rotate", false);
+
     /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */
     private static final int DEFAULT_TIMEOUT_MS = 5000;
     /** Less duration for CHANGE type because it does not involve app startup. */
@@ -203,6 +208,11 @@
         return mTransitionPlayer != null;
     }
 
+    /** @return {@code true} if using shell-transitions rotation instead of fixed-rotation. */
+    boolean useShellTransitionsRotation() {
+        return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION;
+    }
+
     /**
      * @return {@code true} if transition is actively collecting changes. This is {@code false}
      * once a transition is playing
@@ -246,6 +256,17 @@
         return false;
     }
 
+    /** @return {@code true} if wc is in a participant subtree */
+    boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
+        if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
+            return true;
+        }
+        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+            if (mPlayingTransitions.get(i).isOnDisplay(dc)) return true;
+        }
+        return false;
+    }
+
     /**
      * @return {@code true} if {@param ar} is part of a transient-launch activity in an active
      * transition.
@@ -260,6 +281,21 @@
         return false;
     }
 
+    /**
+     * Temporary work-around to deal with integration of legacy fixed-rotation. Returns whether
+     * the activity was visible before the collecting transition.
+     * TODO: at-least replace the polling mechanism.
+     */
+    boolean wasVisibleAtStart(@NonNull ActivityRecord ar) {
+        if (mCollectingTransition == null) return ar.isVisible();
+        final Transition.ChangeInfo ci = mCollectingTransition.mChanges.get(ar);
+        if (ci == null) {
+            // not part of transition, so use current state.
+            return ar.isVisible();
+        }
+        return ci.mVisible;
+    }
+
     @WindowManager.TransitionType
     int getCollectingTransitionType() {
         return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE;
@@ -484,6 +520,11 @@
         }
     }
 
+    void setSeamlessRotation(@NonNull WindowContainer wc) {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.setSeamlessRotation(wc);
+    }
+
     void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
         final Transition transition = Transition.fromBinder(token);
         if (transition == null || !mPlayingTransitions.contains(transition)) {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 36bb55e..6ee30bb 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -111,6 +111,14 @@
             changed = true;
         }
         if (mTransitionController.isShellTransitionsEnabled()) {
+            // Apply legacy fixed rotation to wallpaper if it is becoming visible
+            if (!mTransitionController.useShellTransitionsRotation() && changed && visible) {
+                final WindowState wallpaperTarget =
+                        mDisplayContent.mWallpaperController.getWallpaperTarget();
+                if (wallpaperTarget != null && wallpaperTarget.mToken.hasFixedRotationTransform()) {
+                    linkFixedRotationTransform(wallpaperTarget.mToken);
+                }
+            }
             return changed;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 073a508..5bfd546 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -80,6 +80,16 @@
     }
 
     @Override
+    public boolean getShowBackground() {
+        return mAnimation.getShowBackground();
+    }
+
+    @Override
+    public int getBackgroundColor() {
+        return mAnimation.getBackgroundColor();
+    }
+
+    @Override
     public long getDuration() {
         return mAnimation.computeDurationHint();
     }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8a373bf..e1746cc 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -39,6 +39,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
+import static com.android.server.wm.AppTransition.isActivityTransitOld;
 import static com.android.server.wm.AppTransition.isTaskTransitOld;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -78,13 +79,14 @@
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Pools;
+import android.util.RotationUtils;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
-import android.view.InsetsState;
 import android.view.MagnificationSpec;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
 import android.view.SurfaceControlViewHost;
@@ -98,6 +100,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.wm.SurfaceAnimator.Animatable;
@@ -197,6 +200,7 @@
 
     private final Point mTmpPos = new Point();
     protected final Point mLastSurfacePosition = new Point();
+    protected @Surface.Rotation int mLastDeltaRotation = Surface.ROTATION_0;
 
     /** Total number of elements in this subtree, including our own hierarchy element. */
     private int mTreeWeight = 1;
@@ -473,6 +477,7 @@
         t.remove(mSurfaceControl);
         // Clear the last position so the new SurfaceControl will get correct position
         mLastSurfacePosition.set(0, 0);
+        mLastDeltaRotation = Surface.ROTATION_0;
 
         final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null)
                 .setContainerLayer()
@@ -644,6 +649,7 @@
             getSyncTransaction().remove(mSurfaceControl);
             setSurfaceControl(null);
             mLastSurfacePosition.set(0, 0);
+            mLastDeltaRotation = Surface.ROTATION_0;
             scheduleAnimation();
         }
         if (mOverlayHost != null) {
@@ -2786,7 +2792,7 @@
             @TransitionOldType int transit, boolean isVoiceInteraction,
             @Nullable ArrayList<WindowContainer> sources) {
         final Task task = asTask();
-        if (task != null && !enter && !task.isHomeOrRecentsRootTask()) {
+        if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) {
             final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
             final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null
                     && imeTarget.getWindow().getTask() == task;
@@ -2816,6 +2822,25 @@
                 }
             }
 
+            final ActivityRecord activityRecord = asActivityRecord();
+            if (activityRecord != null && isActivityTransitOld(transit)
+                    && adapter.getShowBackground()) {
+                final @ColorInt int backgroundColorForTransition;
+                if (adapter.getBackgroundColor() != 0) {
+                    // If available use the background color provided through getBackgroundColor
+                    // which if set originates from a call to overridePendingAppTransition.
+                    backgroundColorForTransition = adapter.getBackgroundColor();
+                } else {
+                    // Otherwise default to the window's background color if provided through
+                    // the theme as the background color for the animation - the top most window
+                    // with a valid background color and showBackground set takes precedence.
+                    final Task arTask = activityRecord.getTask();
+                    backgroundColorForTransition = ColorUtils.setAlphaComponent(
+                            arTask.getTaskDescription().getBackgroundColor(), 255);
+                }
+                animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition);
+            }
+
             animationRunnerBuilder.build()
                     .startAnimation(getPendingTransaction(), adapter, !isVisible(),
                             ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
@@ -3127,12 +3152,43 @@
         }
 
         getRelativePosition(mTmpPos);
-        if (mTmpPos.equals(mLastSurfacePosition)) {
+        final int deltaRotation = getRelativeDisplayRotation();
+        if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
             return;
         }
 
         t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
+        // set first, since we don't want rotation included in this (for now).
         mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
+
+        if (mTransitionController.isShellTransitionsEnabled()
+                && !mTransitionController.useShellTransitionsRotation()) {
+            if (deltaRotation != Surface.ROTATION_0) {
+                updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
+            } else if (deltaRotation != mLastDeltaRotation) {
+                t.setMatrix(mSurfaceControl, 1, 0, 0, 1);
+            }
+        }
+        mLastDeltaRotation = deltaRotation;
+    }
+
+    /**
+     * Updates the surface transform based on a difference in displayed-rotation from its parent.
+     * @param positionLeash If non-null, the rotated position will be set on this surface instead
+     *                      of the window surface. {@see WindowToken#getOrCreateFixedRotationLeash}.
+     */
+    protected void updateSurfaceRotation(Transaction t, @Surface.Rotation int deltaRotation,
+            @Nullable SurfaceControl positionLeash) {
+        // parent must be non-null otherwise deltaRotation would be 0.
+        RotationUtils.rotateSurface(t, mSurfaceControl, deltaRotation);
+        mTmpPos.set(mLastSurfacePosition.x, mLastSurfacePosition.y);
+        final Rect parentBounds = getParent().getBounds();
+        final boolean flipped = (deltaRotation % 2) != 0;
+        RotationUtils.rotatePoint(mTmpPos, deltaRotation,
+                flipped ? parentBounds.height() : parentBounds.width(),
+                flipped ? parentBounds.width() : parentBounds.height());
+        t.setPosition(positionLeash != null ? positionLeash : mSurfaceControl,
+                mTmpPos.x, mTmpPos.y);
     }
 
     @VisibleForTesting
@@ -3170,6 +3226,16 @@
         }
     }
 
+    /** @return the difference in displayed-rotation from parent. */
+    @Surface.Rotation
+    int getRelativeDisplayRotation() {
+        final WindowContainer parent = getParent();
+        if (parent == null) return Surface.ROTATION_0;
+        final int rotation = getWindowConfiguration().getDisplayRotation();
+        final int parentRotation = parent.getWindowConfiguration().getDisplayRotation();
+        return RotationUtils.deltaRotation(rotation, parentRotation);
+    }
+
     void waitForAllWindowsDrawn() {
         forAllWindows(w -> {
             w.requestDrawIfNeeded(mWaitingForDrawn);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cacff5a..4d1bc22 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -421,7 +421,7 @@
             "persist.wm.enable_remote_keyguard_animation";
 
     private static final int sEnableRemoteKeyguardAnimation =
-            SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
+            SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
 
     /**
      * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
@@ -1364,6 +1364,7 @@
         float lightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
         float ambientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0);
         float spotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0);
+        a.recycle();
         float[] ambientColor = {0.f, 0.f, 0.f, ambientShadowAlpha};
         float[] spotColor = {0.f, 0.f, 0.f, spotShadowAlpha};
         SurfaceControl.setGlobalShadowSettings(ambientColor, spotColor, lightY, lightZ,
@@ -2893,16 +2894,6 @@
         }
     }
 
-    void setNewDisplayOverrideConfiguration(Configuration overrideConfig,
-            @NonNull DisplayContent dc) {
-        if (dc.mWaitingForConfig) {
-            dc.mWaitingForConfig = false;
-            mLastFinishedFreezeSource = "new-config";
-        }
-
-        mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, dc);
-    }
-
     // TODO(multi-display): remove when no default display use case.
     void prepareAppTransitionNone() {
         if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) {
@@ -3683,7 +3674,8 @@
      * Sets the touch mode state.
      *
      * To be able to change touch mode state, the caller must either own the focused window, or must
-     * have the MODIFY_TOUCH_MODE_STATE permission.
+     * have the MODIFY_TOUCH_MODE_STATE permission. Instrumented processes are allowed to switch
+     * touch mode at any time.
      *
      * @param mode the touch mode to set
      */
@@ -3695,8 +3687,9 @@
             }
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
-            final boolean hasPermission = checkCallingPermission(MODIFY_TOUCH_MODE_STATE,
-                    "setInTouchMode()");
+
+            final boolean hasPermission = mAtmService.isInstrumenting(pid)
+                    || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()");
             final long token = Binder.clearCallingIdentity();
             try {
                 if (mInputManager.setInTouchMode(mode, pid, uid, hasPermission)) {
@@ -8350,9 +8343,9 @@
      * views.
      */
     void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
-                           SurfaceControl surface, IWindow window, IBinder hostInputToken,
-                           int flags, int privateFlags, int type, IBinder focusGrantToken,
-                           InputChannel outInputChannel) {
+            SurfaceControl surface, IWindow window, IBinder hostInputToken,
+            int flags, int privateFlags, int type, IBinder focusGrantToken,
+            String inputHandleName, InputChannel outInputChannel) {
         final InputApplicationHandle applicationHandle;
         final String name;
         final InputChannel clientChannel;
@@ -8360,7 +8353,7 @@
             EmbeddedWindowController.EmbeddedWindow win =
                     new EmbeddedWindowController.EmbeddedWindow(session, this, window,
                             mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type,
-                            displayId, focusGrantToken);
+                            displayId, focusGrantToken, inputHandleName);
             clientChannel = win.openInputChannel();
             mEmbeddedWindowController.add(clientChannel.getToken(), win);
             applicationHandle = win.getApplicationHandle();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 27024ce..4c7891b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -19,6 +19,8 @@
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.isStartResultSuccessful;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION;
+import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
@@ -1146,7 +1148,11 @@
         final PendingTransaction pt = mPendingTransactions.remove(0);
         pt.startSync();
         // Post this so that the now-playing transition setup isn't interrupted.
-        mService.mH.post(pt::startTransaction);
+        mService.mH.post(() -> {
+            synchronized (mGlobalLock) {
+                pt.startTransaction();
+            }
+        });
     }
 
     @Override
@@ -1220,10 +1226,14 @@
         while (entries.hasNext()) {
             final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
             // Only allow to apply changes to TaskFragment that is created by this organizer.
-            enforceTaskFragmentOrganized(func, WindowContainer.fromBinder(entry.getKey()),
-                    organizer);
+            WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
+            enforceTaskFragmentOrganized(func, wc, organizer);
+            enforceTaskFragmentConfigChangeAllowed(func, wc, entry.getValue(), organizer);
         }
 
+        // TODO(b/197364677): Enforce safety of hierarchy operations in untrusted mode. E.g. one
+        // could first change a trusted TF, and then start/reparent untrusted activity there.
+
         // Hierarchy changes
         final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
         for (int i = hops.size() - 1; i >= 0; i--) {
@@ -1293,6 +1303,57 @@
         }
     }
 
+    /**
+     * Makes sure that SurfaceControl transactions and the ability to set bounds outside of the
+     * parent bounds are not allowed for embedding without full trust between the host and the
+     * target.
+     * TODO(b/197364677): Allow SC transactions when the client-driven animations are protected from
+     * tapjacking.
+     */
+    private void enforceTaskFragmentConfigChangeAllowed(String func, @Nullable WindowContainer wc,
+            WindowContainerTransaction.Change change, ITaskFragmentOrganizer organizer) {
+        if (wc == null) {
+            Slog.e(TAG, "Attempt to operate on task fragment that no longer exists");
+            return;
+        }
+        // Check if TaskFragment is embedded in fully trusted mode
+        if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) {
+            // Fully trusted, no need to check further
+            return;
+        }
+
+        if (change == null) {
+            return;
+        }
+        final int changeMask = change.getChangeMask();
+        if ((changeMask & (CHANGE_BOUNDS_TRANSACTION | CHANGE_BOUNDS_TRANSACTION_RECT)) != 0) {
+            String msg = "Permission Denial: " + func + " from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " trying to apply SurfaceControl changes to TaskFragment in non-trusted "
+                    + "embedding mode, TaskFragmentOrganizer=" + organizer;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        if (change.getWindowSetMask() == 0) {
+            // Nothing else to check.
+            return;
+        }
+        WindowConfiguration requestedWindowConfig = change.getConfiguration().windowConfiguration;
+        WindowContainer wcParent = wc.getParent();
+        if (wcParent == null) {
+            Slog.e(TAG, "Attempt to set bounds on task fragment that has no parent");
+            return;
+        }
+        if (!wcParent.getBounds().contains(requestedWindowConfig.getBounds())) {
+            String msg = "Permission Denial: " + func + " from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " trying to apply bounds outside of parent for non-trusted host,"
+                    + " TaskFragmentOrganizer=" + organizer;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+    }
+
     void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
             @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) {
         final ActivityRecord ownerActivity =
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 11a6141..498eaab 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2385,6 +2385,7 @@
         dc.getDisplayPolicy().removeWindowLw(this);
 
         disposeInputChannel();
+        mOnBackInvokedCallback = null;
 
         mSession.windowRemovedLocked();
         try {
@@ -2438,6 +2439,7 @@
 
         try {
             disposeInputChannel();
+            mOnBackInvokedCallback = null;
 
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index db231f6..f398034 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -44,6 +44,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager.LayoutParams.WindowType;
 import android.window.WindowContext;
@@ -99,6 +100,7 @@
     final boolean mOwnerCanManageAppTokens;
 
     private FixedRotationTransformState mFixedRotationTransformState;
+    private SurfaceControl mFixedRotationTransformLeash;
 
     /**
      * When set to {@code true}, this window token is created from {@link WindowContext}
@@ -521,8 +523,14 @@
         if (state == null) {
             return;
         }
-
-        state.resetTransform();
+        if (!mTransitionController.isShellTransitionsEnabled()) {
+            state.resetTransform();
+        } else {
+            // Remove all the leashes
+            for (int i = state.mAssociatedTokens.size() - 1; i >= 0; --i) {
+                state.mAssociatedTokens.get(i).removeFixedRotationLeash();
+            }
+        }
         // Clear the flag so if the display will be updated to the same orientation, the transform
         // won't take effect.
         state.mIsTransforming = false;
@@ -554,6 +562,43 @@
     }
 
     /**
+     * Gets or creates a leash which can be treated as if this window is not-rotated. This is
+     * used to adapt mismatched-rotation surfaces into code that expects all windows to share
+     * the same rotation.
+     */
+    @Nullable
+    SurfaceControl getOrCreateFixedRotationLeash() {
+        if (!mTransitionController.isShellTransitionsEnabled()) return null;
+        final int rotation = getRelativeDisplayRotation();
+        if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash;
+        if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash;
+
+        final SurfaceControl.Transaction t = getSyncTransaction();
+        final SurfaceControl leash = makeSurface().setContainerLayer()
+                .setParent(getParentSurfaceControl())
+                .setName(getSurfaceControl() + " - rotation-leash")
+                .setHidden(false)
+                .setEffectLayer()
+                .setCallsite("WindowToken.getOrCreateFixedRotationLeash")
+                .build();
+        t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y);
+        t.show(leash);
+        t.reparent(getSurfaceControl(), leash);
+        t.setAlpha(getSurfaceControl(), 1.f);
+        mFixedRotationTransformLeash = leash;
+        updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash);
+        return mFixedRotationTransformLeash;
+    }
+
+    void removeFixedRotationLeash() {
+        if (mFixedRotationTransformLeash == null) return;
+        final SurfaceControl.Transaction t = getSyncTransaction();
+        t.reparent(getSurfaceControl(), getParentSurfaceControl());
+        t.remove(mFixedRotationTransformLeash);
+        mFixedRotationTransformLeash = null;
+    }
+
+    /**
      * It is called when the window is using fixed rotation transform, and before display applies
      * the same rotation, the rotation change for display is canceled, e.g. the orientation from
      * sensor is updated to previous direction.
@@ -575,7 +620,7 @@
     @Override
     void updateSurfacePosition(SurfaceControl.Transaction t) {
         super.updateSurfacePosition(t);
-        if (isFixedRotationTransforming()) {
+        if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
             final ActivityRecord r = asActivityRecord();
             final Task rootTask = r != null ? r.getRootTask() : null;
             // Don't transform the activity in PiP because the PiP task organizer will handle it.
@@ -588,6 +633,20 @@
     }
 
     @Override
+    protected void updateSurfaceRotation(SurfaceControl.Transaction t,
+            @Surface.Rotation int deltaRotation, SurfaceControl positionLeash) {
+        final ActivityRecord r = asActivityRecord();
+        if (r != null) {
+            final Task rootTask = r.getRootTask();
+            // Don't transform the activity in PiP because the PiP task organizer will handle it.
+            if (rootTask != null && rootTask.inPinnedWindowingMode()) {
+                return;
+            }
+        }
+        super.updateSurfaceRotation(t, deltaRotation, positionLeash);
+    }
+
+    @Override
     void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) {
         // Keep the transformed position to animate because the surface will show in different
         // rotation than the animator of leash.
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 95e1aec..0d49f5f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -160,7 +160,7 @@
         "[email protected]",
         "android.hardware.graphics.common-V3-ndk",
         "[email protected]",
-        "[email protected]",
+        "android.hardware.input.processor-V1-ndk",
         "[email protected]",
         "[email protected]",
         "android.hardware.memtrack-V1-ndk",
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index adc91fc..8197b67 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -243,22 +243,34 @@
             xAbsSetup.code = ABS_MT_POSITION_X;
             xAbsSetup.absinfo.maximum = screenWidth - 1;
             xAbsSetup.absinfo.minimum = 0;
-            ioctl(fd, UI_ABS_SETUP, xAbsSetup);
+            if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno));
+                return -errno;
+            }
             uinput_abs_setup yAbsSetup;
             yAbsSetup.code = ABS_MT_POSITION_Y;
             yAbsSetup.absinfo.maximum = screenHeight - 1;
             yAbsSetup.absinfo.minimum = 0;
-            ioctl(fd, UI_ABS_SETUP, yAbsSetup);
+            if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno));
+                return -errno;
+            }
             uinput_abs_setup majorAbsSetup;
             majorAbsSetup.code = ABS_MT_TOUCH_MAJOR;
             majorAbsSetup.absinfo.maximum = screenWidth - 1;
             majorAbsSetup.absinfo.minimum = 0;
-            ioctl(fd, UI_ABS_SETUP, majorAbsSetup);
+            if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno));
+                return -errno;
+            }
             uinput_abs_setup pressureAbsSetup;
             pressureAbsSetup.code = ABS_MT_PRESSURE;
             pressureAbsSetup.absinfo.maximum = 255;
             pressureAbsSetup.absinfo.minimum = 0;
-            ioctl(fd, UI_ABS_SETUP, pressureAbsSetup);
+            if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
+                return -errno;
+            }
         }
         if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
             ALOGE("Error creating uinput device: %s", strerror(errno));
@@ -266,6 +278,7 @@
         }
     } else {
         // UI_DEV_SETUP was not introduced until version 5. Try setting up manually.
+        ALOGI("Falling back to version %d manual setup", version);
         uinput_user_dev fallback;
         memset(&fallback, 0, sizeof(fallback));
         strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 80011d1..f8ace0c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9616,18 +9616,22 @@
                     "Cannot set the profile owner on a user which is already set-up");
 
             if (!mIsWatch) {
-                // Only the default supervision profile owner can be set as profile owner after SUW
+                final String supervisionRolePackage = mContext.getResources().getString(
+                        com.android.internal.R.string.config_systemSupervision);
+                // Only the default supervision profile owner or supervision role holder
+                // can be set as profile owner after SUW
                 final String supervisor = mContext.getResources().getString(
                         com.android.internal.R.string
                                 .config_defaultSupervisionProfileOwnerComponent);
-                if (supervisor == null) {
+                if (supervisor == null && supervisionRolePackage == null) {
                     throw new IllegalStateException("Unable to set profile owner post-setup, no"
                             + "default supervisor profile owner defined");
                 }
 
                 final ComponentName supervisorComponent = ComponentName.unflattenFromString(
                         supervisor);
-                if (!owner.equals(supervisorComponent)) {
+                if (!owner.equals(supervisorComponent)
+                        && !owner.getPackageName().equals(supervisionRolePackage)) {
                     throw new IllegalStateException("Unable to set non-default profile owner"
                             + " post-setup " + owner);
                 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a7539b5..5c3721d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -264,6 +264,10 @@
             "com.android.server.companion.virtual.VirtualDeviceManagerService";
     private static final String STATS_COMPANION_APEX_PATH =
             "/apex/com.android.os.statsd/javalib/service-statsd.jar";
+    private static final String SCHEDULING_APEX_PATH =
+            "/apex/com.android.scheduling/javalib/service-scheduling.jar";
+    private static final String REBOOT_READINESS_LIFECYCLE_CLASS =
+            "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle";
     private static final String CONNECTIVITY_SERVICE_APEX_PATH =
             "/apex/com.android.tethering/javalib/service-connectivity.jar";
     private static final String STATS_COMPANION_LIFECYCLE_CLASS =
@@ -2575,6 +2579,12 @@
                 STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
         t.traceEnd();
 
+        // Reboot Readiness
+        t.traceBegin("StartRebootReadinessManagerService");
+        mSystemServiceManager.startServiceFromJar(
+                REBOOT_READINESS_LIFECYCLE_CLASS, SCHEDULING_APEX_PATH);
+        t.traceEnd();
+
         // Statsd pulled atoms
         t.traceBegin("StartStatsPullAtomService");
         mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
diff --git a/services/proguard.flags b/services/proguard.flags
index 5d01d3e..0e081f1 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -1,21 +1,105 @@
-# TODO(b/196084106): Refine and optimize this configuration. Note that this
+# TODO(b/210510433): Refine and optimize this configuration. Note that this
 # configuration is only used when `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA=true`.
--keep,allowoptimization,allowaccessmodification class ** {
-  !synthetic *;
-}
 
-# Various classes subclassed in ethernet-service (avoid marking final).
--keep public class android.net.** { *; }
-
-# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing).
--keep public class com.android.server.utils.Slogf { *; }
+# Preserve line number information for debugging stack traces.
+-keepattributes SourceFile,LineNumberTable
 
 # Allows making private and protected methods/fields public as part of
 # optimization. This enables inlining of trivial getter/setter methods.
 -allowaccessmodification
 
-# Disallow accessmodification for soundtrigger classes. Logging via reflective
-# public member traversal can cause infinite loops. See b/210901706.
--keep,allowoptimization class com.android.server.soundtrigger_middleware.** {
-  !synthetic *;
+# Process entrypoint
+-keep class com.android.server.SystemServer {
+  public static void main(java.lang.String[]);
 }
+
+# APIs referenced by dependent JAR files and modules
+-keep @interface android.annotation.SystemApi
+-keep @android.annotation.SystemApi class * {
+  public protected *;
+}
+-keepclasseswithmembers class * {
+  @android.annotation.SystemApi *;
+}
+
+# Derivatives of SystemService and other services created via reflection
+-keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService {
+  public <methods>;
+}
+-keep,allowoptimization,allowaccessmodification class * extends com.android.server.devicepolicy.BaseIDevicePolicyManager {
+  public <init>(...);
+}
+-keep,allowoptimization,allowaccessmodification class com.android.server.wallpaper.WallpaperManagerService {
+  public <init>(...);
+}
+
+# Binder interfaces
+-keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface
+-keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface
+
+# Global entities normally kept through explicit Manifest entries
+# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/AndroidManifest.xml,
+# by including that manifest with the library rule that triggers optimization.
+-keep,allowoptimization,allowaccessmodification class * extends android.app.backup.BackupAgent
+-keep,allowoptimization,allowaccessmodification class * extends android.content.BroadcastReceiver
+-keep,allowoptimization,allowaccessmodification class * extends android.content.ContentProvider
+
+# Various classes subclassed in or referenced via JNI in ethernet-service
+-keep public class android.net.** { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; }
+-keep,allowoptimization,allowaccessmodification public class com.android.server.net.IpConfigStore { *; }
+-keep,allowoptimization,allowaccessmodification public class com.android.server.net.BaseNetworkObserver { *; }
+
+# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing)
+-keep public class com.android.server.utils.Slogf { *; }
+
+# Notification extractors
+# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/res/values/config.xml.
+-keep,allowoptimization,allowaccessmodification public class com.android.server.notification.** implements com.android.server.notification.NotificationSignalExtractor
+
+# JNI keep rules
+# TODO(b/210510433): Revisit and fix with @Keep, or consider auto-generating from
+# frameworks/base/services/core/jni/onload.cpp.
+-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.BroadcastRadioService { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.Convert { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.Tuner { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.TunerCallback { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssConfiguration$HalInterfaceVersion { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.ExternalCaptureStateTracker { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.storage.AppFuseBridge { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.tv.TvInputHal { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbMidiDevice { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; }
+-keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** {
+  *** *FromNative(...);
+}
+-keep,allowoptimization,allowaccessmodification class com.android.server.input.InputManagerService {
+  <methods>;
+}
+-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbHostManager {
+  *** usbDeviceRemoved(...);
+  *** usbDeviceAdded(...);
+}
+-keep,allowoptimization,allowaccessmodification class **.*NativeWrapper* { *; }
+
+# Miscellaneous reflection keep rules
+# TODO(b/210510433): Revisit and fix with @Keep.
+-keep,allowoptimization,allowaccessmodification class com.android.server.usage.AppStandbyController {
+  public <init>(...);
+}
+-keep,allowoptimization,allowaccessmodification class android.hardware.usb.gadget.** { *; }
+
+# Needed when optimizations enabled
+# TODO(b/210510433): Revisit and fix with @Keep.
+-keep,allowoptimization,allowaccessmodification class com.android.server.SystemService { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.SystemService$TargetUser { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.usage.StorageStatsManagerLocal { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.internal.util.** { *; }
+-keep,allowoptimization,allowaccessmodification class android.os.** { *; }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index cc663d9..11300cec 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -508,7 +508,8 @@
             AndroidPackage::shouldInheritKeyStoreKeys,
             ParsingPackage::setInheritKeyStoreKeys,
             true
-        )
+        ),
+        getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT"))
     )
 
     override fun initialObject() = PackageImpl.forParsing(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index a89b717..5180786 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -53,7 +53,7 @@
         ParsedActivity::getTaskAffinity,
         ParsedActivity::getTheme,
         ParsedActivity::getUiOptions,
-        ParsedActivity::isSupportsSizeChanges,
+        ParsedActivity::isSupportsSizeChanges
     )
 
     override fun mainComponentSubclassExtraParams() = listOf(
@@ -73,6 +73,7 @@
                     ActivityInfo.WindowLayout::minHeight
                 )
             }
-        )
+        ),
+        getter(ParsedActivity::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT"))
     )
 }
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 75669d5..3e60af3 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -53,6 +53,7 @@
         "service-jobscheduler",
         "service-permission.impl",
         "service-supplementalprocess.impl",
+        "services.companion",
         "services.core",
         "services.devicepolicy",
         "services.net",
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 5885470..f05658b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -108,6 +108,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
@@ -124,6 +125,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManagerInternal;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -389,6 +391,7 @@
                 .mockStatic(LocalServices.class)
                 .spyStatic(Looper.class)
                 .mockStatic(MetricsHelper.class)
+                .mockStatic(PermissionChecker.class)
                 .mockStatic(PermissionManagerService.class)
                 .mockStatic(ServiceManager.class)
                 .mockStatic(Settings.Global.class)
@@ -445,6 +448,10 @@
         doReturn(true)
                 .when(() -> DateFormat.is24HourFormat(eq(mMockContext), anyInt()));
 
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                () -> PermissionChecker.checkPermissionForPreflight(any(),
+                        eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), anyInt(), anyString()));
+
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
 
         registerAppIds(new String[]{TEST_CALLING_PACKAGE},
@@ -2158,6 +2165,7 @@
 
     @Test
     public void canScheduleExactAlarmsBinderCall() throws RemoteException {
+        // Policy permission is denied in setUp().
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
         // No permission, no exemption.
@@ -2168,6 +2176,14 @@
         mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
         assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
+        // Policy permission only, no exemption.
+        mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
+        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+                () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext),
+                        eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), eq(TEST_CALLING_UID),
+                        eq(TEST_CALLING_PACKAGE)));
+        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
         // Permission, no exemption.
         mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
@@ -2699,7 +2715,8 @@
         mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false);
 
         // No permission revoked.
-        verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString());
+        verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString(),
+                anyBoolean());
 
         // Permission got granted only for (appId1, userId2).
         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -2754,14 +2771,14 @@
 
         // Permission got revoked only for (appId1, userId2)
         verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
-                eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]));
+                eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true));
         verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
-                eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]));
+                eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true));
         verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
-                eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]));
+                eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true));
 
         verify(mService).removeExactAlarmsOnPermissionRevokedLocked(
-                eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]));
+                eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true));
     }
 
     @Test
@@ -2774,7 +2791,7 @@
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
         assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
         verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID,
-                TEST_CALLING_PACKAGE);
+                TEST_CALLING_PACKAGE, true);
     }
 
     @Test
@@ -2859,7 +2876,8 @@
                 null);
         assertEquals(6, mService.mAlarmStore.size());
 
-        mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+        mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+                true);
 
         final ArrayList<Alarm> remaining = mService.mAlarmStore.asList();
         assertEquals(3, remaining.size());
@@ -3080,7 +3098,7 @@
                 SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters);
 
         final Intent packageAdded = new Intent(Intent.ACTION_PACKAGE_ADDED)
-                .setPackage(TEST_CALLING_PACKAGE)
+                .setData(Uri.fromParts("package", TEST_CALLING_PACKAGE, null))
                 .putExtra(Intent.EXTRA_REPLACING, true);
         mPackageChangesReceiver.onReceive(mMockContext, packageAdded);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index a112baf..816dbdb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -23,6 +23,7 @@
 import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManager.isLowRamDeviceStatic;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
@@ -46,9 +47,11 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
-import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_BG;
-import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FG;
-import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FGS;
+import static com.android.server.am.AppBatteryTracker.AppBatteryPolicy.getFloatArray;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE;
+import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATT_DIMENS;
 import static com.android.server.am.AppRestrictionController.STOCK_PM_FLAGS;
 
 import static org.junit.Assert.assertEquals;
@@ -107,12 +110,14 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
 import com.android.server.AppStateTracker;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
 import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates;
 import com.android.server.am.AppBatteryExemptionTracker.UidStateEventWithBattery;
 import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage;
 import com.android.server.am.AppBindServiceEventsTracker.AppBindServiceEventsPolicy;
 import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy;
 import com.android.server.am.AppFGSTracker.AppFGSPolicy;
@@ -552,28 +557,34 @@
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
                     DeviceConfig::getBoolean,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+                    mContext.getResources().getBoolean(
+                            R.bool.config_bg_current_drain_monitor_enabled));
             bgCurrentDrainMonitor.set(true);
 
             bgCurrentDrainWindow = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
                     DeviceConfig::getLong,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+                    (long) mContext.getResources().getInteger(
+                            R.integer.config_bg_current_drain_window));
             bgCurrentDrainWindow.set(windowMs);
 
             bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
                     DeviceConfig::getFloat,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+                    getFloatArray(mContext.getResources().obtainTypedArray(
+                            R.array.config_bg_current_drain_threshold_to_restricted_bucket))[
+                            isLowRamDeviceStatic() ? 1 : 0]);
             bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
 
             bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
                     DeviceConfig::getFloat,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+                    getFloatArray(mContext.getResources().obtainTypedArray(
+                            R.array.config_bg_current_drain_threshold_to_bg_restricted))[
+                            isLowRamDeviceStatic() ? 1 : 0]);
             bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
 
             mCurrentTimeMillis = 10_000L;
@@ -1280,64 +1291,76 @@
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
                     DeviceConfig::getBoolean,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+                    mContext.getResources().getBoolean(
+                            R.bool.config_bg_current_drain_monitor_enabled));
             bgCurrentDrainMonitor.set(true);
 
             bgCurrentDrainWindow = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
                     DeviceConfig::getLong,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+                    (long) mContext.getResources().getInteger(
+                            R.integer.config_bg_current_drain_window));
             bgCurrentDrainWindow.set(windowMs);
 
             bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
                     DeviceConfig::getFloat,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+                    getFloatArray(mContext.getResources().obtainTypedArray(
+                            R.array.config_bg_current_drain_threshold_to_restricted_bucket))[
+                            isLowRamDeviceStatic() ? 1 : 0]);
             bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
 
             bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
                     DeviceConfig::getFloat,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+                    getFloatArray(mContext.getResources().obtainTypedArray(
+                            R.array.config_bg_current_drain_threshold_to_bg_restricted))[
+                            isLowRamDeviceStatic() ? 1 : 0]);
             bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
 
             bgCurrentDrainRestrictedBucketHighThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
                     DeviceConfig::getFloat,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD);
+                    getFloatArray(mContext.getResources().obtainTypedArray(
+                            R.array.config_bg_current_drain_high_threshold_to_restricted_bucket))[
+                            isLowRamDeviceStatic() ? 1 : 0]);
             bgCurrentDrainRestrictedBucketHighThreshold.set(restrictBucketHighThreshold);
 
             bgCurrentDrainBgRestrictedHighThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
                     DeviceConfig::getFloat,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+                    getFloatArray(mContext.getResources().obtainTypedArray(
+                            R.array.config_bg_current_drain_high_threshold_to_bg_restricted))[
+                            isLowRamDeviceStatic() ? 1 : 0]);
             bgCurrentDrainBgRestrictedHighThreshold.set(bgRestrictedHighThreshold);
 
             bgMediaPlaybackMinDurationThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
                     DeviceConfig::getLong,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+                    (long) mContext.getResources().getInteger(
+                            R.integer.config_bg_current_drain_media_playback_min_duration));
             bgMediaPlaybackMinDurationThreshold.set(bgMediaPlaybackMinDuration);
 
             bgLocationMinDurationThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
                     DeviceConfig::getLong,
-                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+                    (long) mContext.getResources().getInteger(
+                            R.integer.config_bg_current_drain_location_min_duration));
             bgLocationMinDurationThreshold.set(bgLocationMinDuration);
 
             bgCurrentDrainEventDurationBasedThresholdEnabled = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
                     DeviceConfig::getBoolean,
-                    AppBatteryPolicy
-                            .DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+                    mContext.getResources().getBoolean(
+                            R.bool.config_bg_current_drain_event_duration_based_threshold_enabled));
             bgCurrentDrainEventDurationBasedThresholdEnabled.set(true);
 
             bgBatteryExemptionEnabled = new DeviceConfigSession<>(
@@ -1932,9 +1955,12 @@
     private UidBatteryConsumer mockUidBatteryConsumer(int uid, double bg, double fgs, double fg) {
         UidBatteryConsumer uidConsumer = mock(UidBatteryConsumer.class);
         doReturn(uid).when(uidConsumer).getUid();
-        doReturn(bg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_BG));
-        doReturn(fgs).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FGS));
-        doReturn(fg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FG));
+        doReturn(bg).when(uidConsumer).getConsumedPower(
+                eq(BATT_DIMENS[BATTERY_USAGE_INDEX_BACKGROUND]));
+        doReturn(fgs).when(uidConsumer).getConsumedPower(
+                eq(BATT_DIMENS[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]));
+        doReturn(fg).when(uidConsumer).getConsumedPower(
+                eq(BATT_DIMENS[BATTERY_USAGE_INDEX_FOREGROUND]));
         return uidConsumer;
     }
 
@@ -2234,8 +2260,8 @@
             boolean[] isStart, long[] timestamps, double[] batteryUsage) {
         final LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
         for (int i = 0; i < isStart.length; i++) {
-            result.add(new UidStateEventWithBattery(
-                    isStart[i], timestamps[i], batteryUsage[i], null));
+            result.add(new UidStateEventWithBattery(isStart[i], timestamps[i],
+                        new ImmutableBatteryUsage(0.0d, 0.0d, batteryUsage[i], 0.0d), null));
         }
         return result;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
new file mode 100644
index 0000000..1b9cb28
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraInjectionSession;
+import android.hardware.camera2.CameraManager;
+import android.os.Process;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+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.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class CameraAccessControllerTest {
+    private static final String FRONT_CAMERA = "0";
+    private static final String REAR_CAMERA = "1";
+    private static final String TEST_APP_PACKAGE = "some.package";
+    private static final String OTHER_APP_PACKAGE = "other.package";
+
+    private CameraAccessController mController;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getContext());
+
+    @Mock
+    private CameraManager mCameraManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private VirtualDeviceManagerInternal mDeviceManagerInternal;
+    @Mock
+    private CameraAccessController.CameraAccessBlockedCallback mBlockedCallback;
+
+    private ApplicationInfo mTestAppInfo = new ApplicationInfo();
+    private ApplicationInfo mOtherAppInfo = new ApplicationInfo();
+
+    @Captor
+    ArgumentCaptor<CameraInjectionSession.InjectionStatusCallback> mInjectionCallbackCaptor;
+
+
+    @Before
+    public void setUp() throws PackageManager.NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+        mContext.addMockSystemService(CameraManager.class, mCameraManager);
+        mContext.setMockPackageManager(mPackageManager);
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+        LocalServices.addService(VirtualDeviceManagerInternal.class, mDeviceManagerInternal);
+        mController = new CameraAccessController(mContext, mDeviceManagerInternal,
+                mBlockedCallback);
+        mTestAppInfo.uid = Process.FIRST_APPLICATION_UID;
+        mOtherAppInfo.uid = Process.FIRST_APPLICATION_UID + 1;
+        when(mPackageManager.getApplicationInfo(eq(TEST_APP_PACKAGE), anyInt())).thenReturn(
+                mTestAppInfo);
+        when(mPackageManager.getApplicationInfo(eq(OTHER_APP_PACKAGE), anyInt())).thenReturn(
+                mOtherAppInfo);
+        mController.startObservingIfNeeded();
+    }
+
+    @Test
+    public void onCameraOpened_uidNotRunning_noCameraBlocking() throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(false);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any());
+    }
+
+
+    @Test
+    public void onCameraOpened_uidRunning_cameraBlocked() throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(true);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void onCameraClosed_injectionWasActive_cameraUnblocked() throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(true);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), mInjectionCallbackCaptor.capture());
+        CameraInjectionSession session = mock(CameraInjectionSession.class);
+        mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+
+        mController.onCameraClosed(FRONT_CAMERA);
+        verify(session).close();
+    }
+
+
+    @Test
+    public void onCameraClosed_otherCameraClosed_cameraNotUnblocked() throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(true);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), mInjectionCallbackCaptor.capture());
+        CameraInjectionSession session = mock(CameraInjectionSession.class);
+        mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+
+        mController.onCameraClosed(REAR_CAMERA);
+        verify(session, never()).close();
+    }
+
+    @Test
+    public void onCameraClosed_twoCamerasBlocked_correctCameraUnblocked()
+            throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(true);
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mOtherAppInfo.uid))).thenReturn(true);
+
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        mController.onCameraOpened(REAR_CAMERA, OTHER_APP_PACKAGE);
+
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), mInjectionCallbackCaptor.capture());
+        CameraInjectionSession frontCameraSession = mock(CameraInjectionSession.class);
+        mInjectionCallbackCaptor.getValue().onInjectionSucceeded(frontCameraSession);
+
+        verify(mCameraManager).injectCamera(eq(OTHER_APP_PACKAGE), eq(REAR_CAMERA), anyString(),
+                any(), mInjectionCallbackCaptor.capture());
+        CameraInjectionSession rearCameraSession = mock(CameraInjectionSession.class);
+        mInjectionCallbackCaptor.getValue().onInjectionSucceeded(rearCameraSession);
+
+        mController.onCameraClosed(REAR_CAMERA);
+        verify(frontCameraSession, never()).close();
+        verify(rearCameraSession).close();
+    }
+
+    @Test
+    public void onInjectionError_callbackFires() throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(true);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), mInjectionCallbackCaptor.capture());
+        CameraInjectionSession session = mock(CameraInjectionSession.class);
+        mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+        mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED);
+        verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid));
+    }
+
+    @Test
+    public void twoCameraAccesses_onlyOneOnVirtualDisplay_callbackFiresForCorrectUid()
+            throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(true);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        mController.onCameraOpened(REAR_CAMERA, OTHER_APP_PACKAGE);
+
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), mInjectionCallbackCaptor.capture());
+        CameraInjectionSession session = mock(CameraInjectionSession.class);
+        mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+        mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED);
+        verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index f61d6ca..e4c9f97 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -23,6 +23,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -59,6 +60,10 @@
 import android.os.Build;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.telephony.CellSignalStrength;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
 import android.util.DataUnit;
 
 import com.android.server.LocalServices;
@@ -76,6 +81,7 @@
 
 import java.time.Clock;
 import java.time.ZoneOffset;
+import java.util.Set;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ConnectivityControllerTest {
@@ -302,6 +308,427 @@
     }
 
     @Test
+    public void testStrongEnough_Cellular() {
+        mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
+
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final TelephonyManager telephonyManager = mock(TelephonyManager.class);
+        when(mContext.getSystemService(TelephonyManager.class))
+                .thenReturn(telephonyManager);
+        when(telephonyManager.createForSubscriptionId(anyInt()))
+                .thenReturn(telephonyManager);
+        final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+        doNothing().when(telephonyManager)
+                .registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
+        final ArgumentCaptor<NetworkCallback> callbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+        final Network net = mock(Network.class);
+        final NetworkCapabilities caps = createCapabilitiesBuilder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+                .setSubscriptionIds(Set.of(7357))
+                .build();
+        final JobInfo.Builder baseJobBuilder = createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
+                        DataUnit.MEBIBYTES.toBytes(1))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+        final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
+        final JobStatus jobHigh = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
+        final JobStatus jobDefEarly = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+                now - 1000, now + 100_000);
+        final JobStatus jobDefLate = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+                now - 100_000, now + 1000);
+        final JobStatus jobLow = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
+        final JobStatus jobMin = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+        final JobStatus jobMinRunner = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+
+        final ConnectivityController controller = new ConnectivityController(mService);
+
+        final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+        when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        answerNetwork(generalCallback, null, null, net, caps);
+
+        final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
+                (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
+
+        controller.maybeStartTrackingJobLocked(jobMinRunner, null);
+        controller.prepareForExecutionLocked(jobMinRunner);
+
+        final SignalStrength signalStrength = mock(SignalStrength.class);
+
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(true);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(true);
+        when(mService.isBatteryNotLow()).thenReturn(true);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(true);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(true);
+        when(mService.isBatteryNotLow()).thenReturn(true);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+    }
+
+    @Test
+    public void testStrongEnough_Cellular_CheckDisabled() {
+        mConstants.CONN_USE_CELL_SIGNAL_STRENGTH = false;
+        mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
+
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final TelephonyManager telephonyManager = mock(TelephonyManager.class);
+        when(mContext.getSystemService(TelephonyManager.class))
+                .thenReturn(telephonyManager);
+        when(telephonyManager.createForSubscriptionId(anyInt()))
+                .thenReturn(telephonyManager);
+        final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+        doNothing().when(telephonyManager)
+                .registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
+        final ArgumentCaptor<NetworkCallback> callbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+        final Network net = mock(Network.class);
+        final NetworkCapabilities caps = createCapabilitiesBuilder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+                .setSubscriptionIds(Set.of(7357))
+                .build();
+        final JobInfo.Builder baseJobBuilder = createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
+                        DataUnit.MEBIBYTES.toBytes(1))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+        final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
+        final JobStatus jobHigh = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
+        final JobStatus jobDefEarly = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+                now - 1000, now + 100_000);
+        final JobStatus jobDefLate = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+                now - 100_000, now + 1000);
+        final JobStatus jobLow = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
+        final JobStatus jobMin = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+        final JobStatus jobMinRunner = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+
+        final ConnectivityController controller = new ConnectivityController(mService);
+
+        final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+        when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        answerNetwork(generalCallback, null, null, net, caps);
+
+        final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
+                (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
+
+        controller.maybeStartTrackingJobLocked(jobMinRunner, null);
+        controller.prepareForExecutionLocked(jobMinRunner);
+
+        final SignalStrength signalStrength = mock(SignalStrength.class);
+
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(true);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(true);
+        when(mService.isBatteryNotLow()).thenReturn(true);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(true);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(true);
+        when(mService.isBatteryNotLow()).thenReturn(true);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+    }
+
+    @Test
+    public void testStrongEnough_Cellular_VPN() {
+        mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
+
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final TelephonyManager telephonyManager = mock(TelephonyManager.class);
+        when(mContext.getSystemService(TelephonyManager.class))
+                .thenReturn(telephonyManager);
+        when(telephonyManager.createForSubscriptionId(anyInt()))
+                .thenReturn(telephonyManager);
+        final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+        doNothing().when(telephonyManager)
+                .registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
+        final ArgumentCaptor<NetworkCallback> callbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+        final Network net = mock(Network.class);
+        final NetworkCapabilities caps = createCapabilitiesBuilder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addTransportType(TRANSPORT_VPN)
+                .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+                .setSubscriptionIds(Set.of(7357))
+                .build();
+        final JobInfo.Builder baseJobBuilder = createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
+                        DataUnit.MEBIBYTES.toBytes(1))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+        final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
+        final JobStatus jobHigh = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
+        final JobStatus jobDefEarly = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+                now - 1000, now + 100_000);
+        final JobStatus jobDefLate = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
+                now - 100_000, now + 1000);
+        final JobStatus jobLow = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
+        final JobStatus jobMin = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+        final JobStatus jobMinRunner = createJobStatus(
+                baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
+
+        final ConnectivityController controller = new ConnectivityController(mService);
+
+        final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+        when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
+
+        when(mService.isBatteryCharging()).thenReturn(false);
+        when(mService.isBatteryNotLow()).thenReturn(false);
+        answerNetwork(generalCallback, null, null, net, caps);
+
+        final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
+                (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
+
+        controller.maybeStartTrackingJobLocked(jobMinRunner, null);
+        controller.prepareForExecutionLocked(jobMinRunner);
+
+        final SignalStrength signalStrength = mock(SignalStrength.class);
+
+        when(signalStrength.getLevel()).thenReturn(
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
+
+        // We don't restrict data via VPN over cellular.
+        assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
+        assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
+    }
+
+    @Test
     public void testRelaxed() throws Exception {
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         final JobInfo.Builder job = createJob()
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 0b488b2..5fb3a4e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -33,6 +33,7 @@
 
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -445,6 +446,48 @@
                 mService.getWallpaperDimAmount(), dimAmount, 0.0);
     }
 
+    @Test
+    public void testGetAdjustedWallpaperColorsOnDimming() throws RemoteException {
+        final int testUserId = USER_SYSTEM;
+        mService.switchUser(testUserId, null);
+        mService.setWallpaperComponent(sDefaultWallpaperComponent);
+        WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId);
+
+        // Mock a wallpaper data with color hints that support dark text and dark theme
+        // but not HINT_FROM_BITMAP
+        wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.WHITE), null, null,
+                WallpaperColors.HINT_SUPPORTS_DARK_TEXT | WallpaperColors.HINT_SUPPORTS_DARK_THEME);
+        mService.setWallpaperDimAmount(0.6f);
+        int colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints();
+        // Dimmed wallpaper not extracted from bitmap does not support dark text and dark theme
+        assertNotEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+        assertNotEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME);
+
+        // Remove dimming
+        mService.setWallpaperDimAmount(0f);
+        colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints();
+        // Undimmed wallpaper not extracted from bitmap does support dark text and dark theme
+        assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+        assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME);
+
+        // Mock a wallpaper data with color hints that support dark text and dark theme
+        // and was extracted from bitmap
+        wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.WHITE), null, null,
+                WallpaperColors.HINT_SUPPORTS_DARK_TEXT | WallpaperColors.HINT_SUPPORTS_DARK_THEME
+                        | WallpaperColors.HINT_FROM_BITMAP);
+        mService.setWallpaperDimAmount(0.6f);
+        colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints();
+        // Dimmed wallpaper should still support dark text and dark theme
+        assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+        assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME);
+    }
+
     // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
     // non-current user must not bind to wallpaper service.
     private void verifyNoConnectionBeforeLastUser(int lastUserId) {
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index d9f73d9..53cab9e 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -130,6 +130,19 @@
                android:resource="@xml/test_account_type2_authenticator"/>
         </service>
 
+        <service
+            android:name="com.android.server.dreams.TestDreamService"
+            android:exported="false"
+            android:label="Test Dream" >
+            <intent-filter>
+                <action android:name="android.service.dreams.DreamService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data
+                android:name="android.service.dream"
+                android:resource="@xml/test_dream_metadata" />
+        </service>
+
         <receiver android:name="com.android.server.devicepolicy.ApplicationRestrictionsTest$AdminReceiver"
              android:permission="android.permission.BIND_DEVICE_ADMIN"
              android:exported="true">
diff --git a/packages/SystemUI/res/layout/qs_detail_switch.xml b/services/tests/servicestests/res/xml/test_dream_metadata.xml
similarity index 62%
copy from packages/SystemUI/res/layout/qs_detail_switch.xml
copy to services/tests/servicestests/res/xml/test_dream_metadata.xml
index abb2497..aa054f1 100644
--- a/packages/SystemUI/res/layout/qs_detail_switch.xml
+++ b/services/tests/servicestests/res/xml/test_dream_metadata.xml
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright (C) 2019 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,10 +14,6 @@
   ~ limitations under the License.
   -->
 
-<Switch
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/toggle"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:clickable="false"
-    android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
\ No newline at end of file
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+       android:settingsActivity="com.android.server.dreams/.TestDreamSettingsActivity"
+       android:showClockAndComplications="false" />
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
deleted file mode 100644
index 489e2f7..0000000
--- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
+++ /dev/null
@@ -1,72 +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;
-
-import android.test.AndroidTestCase;
-
-/**
- * Tests for {@link com.android.server.BootReceiver}
- */
-public class BootReceiverTest extends AndroidTestCase {
-    public void testLogLinePotentiallySensitive() throws Exception {
-        /*
-         * Strings to be dropped from the log as potentially sensitive: register dumps, process
-         * names, hardware info.
-         */
-        final String[] becomeNull = {
-            "CPU: 4 PID: 120 Comm: kunit_try_catch Tainted: G        W         5.8.0-rc6+ #7",
-            "Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1 04/01/2014",
-            "[    0.083207] RSP: 0000:ffffffff8fe07ca8 EFLAGS: 00010046 ORIG_RAX: 0000000000000000",
-            "[    0.084709] RAX: 0000000000000000 RBX: ffffffffff240000 RCX: ffffffff815fcf01",
-            "[    0.086109] RDX: dffffc0000000000 RSI: 0000000000000001 RDI: ffffffffff240004",
-            "[    0.087509] RBP: ffffffff8fe07d60 R08: fffffbfff1fc0f21 R09: fffffbfff1fc0f21",
-            "[    0.088911] R10: ffffffff8fe07907 R11: fffffbfff1fc0f20 R12: ffffffff8fe07d38",
-            "R13: 0000000000000001 R14: 0000000000000001 R15: ffffffff8fe07e80",
-            "x29: ffff00003ce07150 x28: ffff80001aa29cc0",
-            "x1 : 0000000000000000 x0 : ffff00000f628000",
-        };
-
-        /* Strings to be left unchanged, including non-sensitive registers and parts of reports. */
-        final String[] leftAsIs = {
-            "FS:  0000000000000000(0000) GS:ffffffff92409000(0000) knlGS:0000000000000000",
-            "[ 69.2366] [ T6006]c7   6006  =======================================================",
-            "[ 69.245688] [ T6006] BUG: KFENCE: out-of-bounds in kfence_handle_page_fault",
-            "[ 69.257816] [ T6006]c7   6006  Out-of-bounds access at 0xffffffca75c45000 ",
-            "[ 69.273536] [ T6006]c7   6006   __do_kernel_fault+0xa8/0x11c",
-            "pc : __mutex_lock+0x428/0x99c ",
-            "sp : ffff00003ce07150",
-            "Call trace:",
-            "",
-        };
-
-        final String[][] stripped = {
-            { "Detected corrupted memory at 0xffffffffb6797ff9 [ 0xac . . . . . . ]:",
-              "Detected corrupted memory at 0xffffffffb6797ff9" },
-        };
-        for (int i = 0; i < becomeNull.length; i++) {
-            assertEquals(BootReceiver.stripSensitiveData(becomeNull[i]), null);
-        }
-
-        for (int i = 0; i < leftAsIs.length; i++) {
-            assertEquals(BootReceiver.stripSensitiveData(leftAsIs[i]), leftAsIs[i]);
-        }
-
-        for (int i = 0; i < stripped.length; i++) {
-            assertEquals(BootReceiver.stripSensitiveData(stripped[i][0]), stripped[i][1]);
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
index 7179c60..022c137 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
@@ -18,8 +18,12 @@
 
 
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE;
 import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
 
 import static org.junit.Assert.assertEquals;
@@ -74,24 +78,37 @@
 
     @Captor
     private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor;
+    @Captor
+    private ArgumentCaptor<List<AccessibilityNodeInfo>> mFindInfosCaptor;
     @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor;
 
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1;
     private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2;
 
-    private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout";
+    private static final String ROOT_FRAME_LAYOUT_DESCRIPTION = "rootFrameLayout";
     private static final String TEXT_VIEW_1_DESCRIPTION = "textView1";
     private static final String TEXT_VIEW_2_DESCRIPTION = "textView2";
+    private static final String CHILD_FRAME_DESCRIPTION = "childFrameLayout";
+    private static final String TEXT_VIEW_3_DESCRIPTION = "textView3";
+    private static final String TEXT_VIEW_4_DESCRIPTION = "textView4";
+    private static final String VIRTUAL_VIEW_1_DESCRIPTION = "virtual descendant 1";
+    private static final String VIRTUAL_VIEW_2_DESCRIPTION = "virtual descendant 2";
+    private static final String VIRTUAL_VIEW_3_DESCRIPTION = "virtual descendant 3";
 
-    private TestFrameLayout mFrameLayout;
+    private TestFrameLayout mRootFrameLayout;
+    private TestFrameLayout mChildFrameLayout;
     private TestTextView mTextView1;
-    private TestTextView2 mTextView2;
+    private TestTextView mTextView2;
+    private TestTextView mTextView3;
+    private TestTextView mTextView4;
 
     private boolean mSendClient1RequestForTextAfterTextPrefetched;
     private boolean mSendClient2RequestForTextAfterTextPrefetched;
     private boolean mSendRequestForTextAndIncludeUnImportantViews;
     private boolean mSendClient1RequestForRootAfterTextPrefetched;
+    private boolean mSendClient2RequestForTextAfterRootPrefetched;
+
     private int mMockClient1InteractionId;
     private int mMockClient2InteractionId;
 
@@ -103,23 +120,45 @@
             final Context context = mInstrumentation.getTargetContext();
             final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
 
-            mFrameLayout = new TestFrameLayout(context);
-            mTextView1 = new TestTextView(context);
-            mTextView2 = new TestTextView2(context);
+            mTextView1 = new TestTextView(context, 1, TEXT_VIEW_1_DESCRIPTION);
+            mTextView2 = new TestTextView(context, 2, TEXT_VIEW_2_DESCRIPTION);
+            mTextView3 = new TestTextView(context, 4, TEXT_VIEW_3_DESCRIPTION);
+            mTextView4 = new TestTextView(context, 5, TEXT_VIEW_4_DESCRIPTION);
 
-            mFrameLayout.addView(mTextView1);
-            mFrameLayout.addView(mTextView2);
+            mChildFrameLayout = new TestFrameLayout(context, 3,
+                    CHILD_FRAME_DESCRIPTION, new ArrayList<>(List.of(mTextView4)));
+            mRootFrameLayout = new TestFrameLayout(context, 0, ROOT_FRAME_LAYOUT_DESCRIPTION,
+                    new ArrayList<>(
+                            List.of(mTextView1, mTextView2, mChildFrameLayout, mTextView3)));
+
+            mRootFrameLayout.addView(mTextView1);
+            mRootFrameLayout.addView(mTextView2);
+            mChildFrameLayout.addView(mTextView4);
+            mRootFrameLayout.addView(mChildFrameLayout);
+            mRootFrameLayout.addView(mTextView3);
+
+            // Layout
+            //                        mRootFrameLayout
+            //               /         |        |              \
+            //       mTextView1 mTextView2 mChildFrameLayout  mTextView3
+            //                                    |
+            //                                mTextView4
 
             // The controller retrieves views through this manager, and registration happens on
-            // when attached to a window, which we don't have. We can simply reference FrameLayout
-            // with ROOT_NODE_ID
+            // when attached to a window, which we don't have. We can simply reference
+            // RootFrameLayout with ROOT_NODE_ID.
             AccessibilityNodeIdManager.getInstance().registerViewWithId(
                     mTextView1, mTextView1.getAccessibilityViewId());
             AccessibilityNodeIdManager.getInstance().registerViewWithId(
                     mTextView2, mTextView2.getAccessibilityViewId());
-
+            AccessibilityNodeIdManager.getInstance().registerViewWithId(
+                    mTextView3, mTextView3.getAccessibilityViewId());
+            AccessibilityNodeIdManager.getInstance().registerViewWithId(
+                    mChildFrameLayout, mChildFrameLayout.getAccessibilityViewId());
+            AccessibilityNodeIdManager.getInstance().registerViewWithId(
+                    mTextView4, mTextView4.getAccessibilityViewId());
             try {
-                viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null);
+                viewRootImpl.setView(mRootFrameLayout, new WindowManager.LayoutParams(), null);
 
             } catch (WindowManager.BadTokenException e) {
                 // activity isn't running, we will ignore BadTokenException.
@@ -137,66 +176,79 @@
                 mTextView1.getAccessibilityViewId());
         AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
                 mTextView2.getAccessibilityViewId());
+        AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
+                mTextView3.getAccessibilityViewId());
+        AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
+                mTextView4.getAccessibilityViewId());
+        AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
+                mChildFrameLayout.getAccessibilityViewId());
     }
 
     /**
      * Tests a basic request for the root node with prefetch flag
-     * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS}
+     * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS_HYBRID}
      *
      * @throws RemoteException
      */
     @Test
     public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants()
             throws RemoteException {
-        // Request for our FrameLayout
+        // Request for our RootFrameLayout.
         sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
         mInstrumentation.waitForIdleSync();
 
-        // Verify we get FrameLayout
+        // Verify we get RootFrameLayout.
         verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
                 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
         AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
-        assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
 
         verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
                 mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
-        // The descendants are our two TextViews
+        // The descendants are RootFrameLayout's 5 descendants.
         List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
-        assertEquals(2, prefetchedNodes.size());
+        assertEquals(5, prefetchedNodes.size());
         assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
         assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
-
+        assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+        assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
+        assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(4).getContentDescription());
     }
 
     /**
-     * Tests a basic request for TestTextView1's node with prefetch flag
-     * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS}
+     * Tests a basic request for TextView1's node with prefetch flag.
+     * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS} and
+     * {@link AccessibilityNodeInfo#FLAG_PREFETCH_ANCESTORS}.
      *
      * @throws RemoteException
      */
     @Test
-    public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings()
+    public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblingsAndParent()
             throws RemoteException {
-        // Request for TextView1
+        // Request for TextView1.
         sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId(
                 mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID),
-                mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
+                mMockClientCallback1, mMockClient1InteractionId,
+                FLAG_PREFETCH_SIBLINGS | FLAG_PREFETCH_ANCESTORS);
         mInstrumentation.waitForIdleSync();
 
-        // Verify we get TextView1
+        // Verify we get TextView1.
         verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
                 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
         AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
         assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
 
-        // Verify the prefetched sibling of TextView1 is TextView2
+        // Verify the prefetched sibling of TextView1 is TextView2, ChildFrameLayout, and TextView3.
+        // The predecessor is RootFrameLayout.
         verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
                 mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
-        // TextView2 is the prefetched sibling
         List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
-        assertEquals(1, prefetchedNodes.size());
-        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+        assertEquals(4, prefetchedNodes.size());
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+        assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+        assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
     }
 
     /**
@@ -213,7 +265,8 @@
     @Test
     public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch()
             throws RemoteException {
-        mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+        mSendClient2RequestForTextAfterRootPrefetched = true;
+        mRootFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() {
             @Override
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
@@ -221,41 +274,50 @@
                         mTextView1.getAccessibilityViewId(),
                         AccessibilityNodeProvider.HOST_VIEW_ID);
 
-                    // Enqueue a request when this node is found from a different service for
-                    // TextView1
+                if (mSendClient2RequestForTextAfterRootPrefetched) {
+                    mSendClient2RequestForTextAfterRootPrefetched = false;
+
+                    // Enqueue a request when this node is found from  client 2 for TextView1.
                     sendNodeRequestToController(nodeId, mMockClientCallback2,
-                            mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
+                            mMockClient2InteractionId,
+                            FLAG_PREFETCH_SIBLINGS);
+                }
             }
         });
-        // Client 1 request for FrameLayout
+        // Client 1 request for RootFrameLayout.
         sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
 
         mInstrumentation.waitForIdleSync();
 
-        // Verify client 1 gets FrameLayout
+        // Verify client 1 gets RootFrameLayout.
         verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
                 mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
         AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
-        assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
 
-        // The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo,
-        // meaning prefetching is interrupted and does not even begin for the first request
+        // The second request is put in the queue in the RootFrameLayout's onInitializeA11yNodeInfo,
+        // meaning prefetching is does not occur for first request.
         verify(mMockClientCallback1, never())
                 .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
 
-        // Verify client 2 gets TextView1
+        // Verify client 2 gets TextView1.
         verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult(
                 mFindInfoCaptor.capture(), eq(mMockClient2InteractionId));
         infoSentToService = mFindInfoCaptor.getValue();
         assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
 
-        // Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS)
+        // Verify the prefetched sibling of TextView1 is TextView2 and ChildFrameLayout
+        // (FLAG_PREFETCH_SIBLINGS). The parent, RootFrameLayout, is also retrieved. Since
+        // predecessors takes priority over siblings, RootFrameLayout is the first node in the list.
         verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult(
                 mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId));
         List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
-        assertEquals(1, prefetchedNodes.size());
-        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+        assertEquals(4, prefetchedNodes.size());
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+        assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+        assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
     }
 
     /**
@@ -282,43 +344,43 @@
             @Override
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
-                info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
                 final long nodeId = AccessibilityNodeInfo.makeNodeId(
                         mTextView1.getAccessibilityViewId(),
                         AccessibilityNodeProvider.HOST_VIEW_ID);
 
                 if (mSendClient1RequestForTextAfterTextPrefetched) {
-                    // Prevent a loop when processing second request
+                    // Prevent a loop when processing this node's second request.
                     mSendClient1RequestForTextAfterTextPrefetched = false;
-                    // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a
-                    // same-client request for TextView1
+                    // TextView1 is prefetched here after the RootFrameLayout is found. Now enqueue
+                    // a same-client request for TextView1.
                     sendNodeRequestToController(nodeId, mMockClientCallback1,
-                            ++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
+                            ++mMockClient1InteractionId,
+                            FLAG_PREFETCH_SIBLINGS | FLAG_PREFETCH_ANCESTORS);
 
                 }
             }
         });
-        // Client 1 requests FrameLayout
+        // Client 1 requests RootFrameLayout.
         sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
 
-        // Flush out all messages
+        // Flush out all messages.
         mInstrumentation.waitForIdleSync();
 
-        // When TextView1 is prefetched for FrameLayout, we put a message on the queue in
-        // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get
+        // When TextView1 is prefetched for RootFrameLayout, we put a message on the queue in
+        // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets
         // two node results for FrameLayout and TextView1.
         verify(mMockClientCallback1, times(2))
                 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
 
         List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues();
-        assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
         assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription());
 
-        // The controller will look at FrameLayout's prefetched nodes and find matching nodes in
-        // pending requests. The prefetched TextView1 matches the second request. This is removed
-        // from the first request's prefetch list, which is now empty. The second
-        // request was removed from queue and prefetching for this request never occurred.
+        // The controller will look at RootFrameLayout's prefetched nodes and find matching nodes in
+        // pending requests. The prefetched TextView1 satisfied the second request. This is removed
+        // from the first request's prefetch list, which is now empty. The second request is removed
+        // from queue.
         verify(mMockClientCallback1, never())
                 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
                         eq(mMockClient1InteractionId - 1));
@@ -347,42 +409,41 @@
             @Override
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
-                info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
                 final long nodeId = AccessibilityNodeInfo.makeNodeId(
-                        mFrameLayout.getAccessibilityViewId(),
+                        mRootFrameLayout.getAccessibilityViewId(),
                         AccessibilityNodeProvider.HOST_VIEW_ID);
 
                 if (mSendClient1RequestForRootAfterTextPrefetched) {
-                    // Prevent a loop when processing second request
+                    // Prevent a loop when processing this node's second request.
                     mSendClient1RequestForRootAfterTextPrefetched = false;
                     // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a
-                    // same-client request for FrameLayout
+                    // same-client request for FrameLayout.
                     sendNodeRequestToController(nodeId, mMockClientCallback1,
-                            ++mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+                            ++mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
 
                 }
             }
         });
-        // Client 1 requests FrameLayout
+        // Client 1 requests RootFrameLayout.
         sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
 
-        // Flush out all messages
+        // Flush out all messages.
         mInstrumentation.waitForIdleSync();
 
-        // When TextView1 is prefetched for FrameLayout, we put a message on the queue in
+        // When TextView1 is prefetched for RootFrameLayout, we put a message on the queue in
         // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets
         // two node results for FrameLayout and TextView1.
         verify(mMockClientCallback1, times(2))
                 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
 
         List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues();
-        assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
-        assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(1).getContentDescription());
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(1).getContentDescription());
 
-        // The controller will look at FrameLayout's prefetched nodes and find matching nodes in
+        // The controller will look at RootFrameLayout's prefetched nodes and find matching nodes in
         // pending requests.  The first requested node (FrameLayout) is also checked, and this
-        // satifies the second request. The second request is removed from queue and prefetching
+        // satisfies the second request. The second request is removed from queue and prefetching
         // for this request never occurs.
         verify(mMockClientCallback1, times(1))
                 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
@@ -406,7 +467,6 @@
             @Override
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
-                info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
                 final long nodeId = AccessibilityNodeInfo.makeNodeId(
                         mTextView1.getAccessibilityViewId(),
                         AccessibilityNodeProvider.HOST_VIEW_ID);
@@ -414,30 +474,30 @@
                 if (mSendClient2RequestForTextAfterTextPrefetched) {
                     mSendClient2RequestForTextAfterTextPrefetched = false;
                     // TextView1 is prefetched here. Now enqueue client 2's request for
-                    // TextView1
+                    // TextView1.
                     sendNodeRequestToController(nodeId, mMockClientCallback2,
                             mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
                 }
             }
         });
-        // Client 1 requests FrameLayout
+        // Client 1 requests RootFrameLayout.
         sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
 
         mInstrumentation.waitForIdleSync();
 
-        // Verify client 1 gets FrameLayout
+        // Verify client 1 gets RootFrameLayout.
         verify(mMockClientCallback1, times(1))
                 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
-        assertEquals(FRAME_LAYOUT_DESCRIPTION,
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION,
                 mFindInfoCaptor.getValue().getContentDescription());
 
-        // Verify client 1 doesn't have prefetched nodes
+        // Verify client 1 doesn't have prefetched nodes.
         verify(mMockClientCallback1, never())
                 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
                         eq(mMockClient1InteractionId));
 
-        // Verify client 2 gets TextView1
+        // Verify client 2 gets TextView1.
         verify(mMockClientCallback2, times(1))
                 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
 
@@ -458,7 +518,6 @@
             @Override
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
-                info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
                 final long nodeId = AccessibilityNodeInfo.makeNodeId(
                         mTextView1.getAccessibilityViewId(),
                         AccessibilityNodeProvider.HOST_VIEW_ID);
@@ -466,17 +525,18 @@
                 if (mSendRequestForTextAndIncludeUnImportantViews) {
                     mSendRequestForTextAndIncludeUnImportantViews = false;
                     // TextView1 is prefetched here for client 1. Now enqueue a request from a
-                    // different client that holds different fetch flags for TextView1
+                    // different client that holds different fetch flags for TextView1.
                     sendNodeRequestToController(nodeId, mMockClientCallback2,
                             mMockClient2InteractionId,
-                            FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
+                            FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+                                    | FLAG_PREFETCH_ANCESTORS);
                 }
             }
         });
 
         // Mockito does not make copies of objects when called. It holds references, so
         // the captor would point to client 2's results after all requests are processed. Verify
-        // prefetched node immediately
+        // prefetched node immediately.
         doAnswer(invocation -> {
             List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0);
             assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription());
@@ -484,39 +544,290 @@
         }).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(),
                 eq(mMockClient1InteractionId));
 
-        // Client 1 requests FrameLayout
+        // Client 1 requests RootFrameLayout.
         sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
 
         mInstrumentation.waitForIdleSync();
 
-        // Verify client 1 gets FrameLayout
+        // Verify client 1 gets RootFrameLayout.
         verify(mMockClientCallback1, times(1))
                 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
                         eq(mMockClient1InteractionId));
 
-        assertEquals(FRAME_LAYOUT_DESCRIPTION,
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION,
                 mFindInfoCaptor.getValue().getContentDescription());
 
         // Verify client 1 has prefetched results. The only prefetched node is TextView1
-        // (from above doAnswer)
+        // (from above doAnswer).
         verify(mMockClientCallback1, times(1))
                 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
                         eq(mMockClient1InteractionId));
 
-        // Verify client 2 gets TextView1
+        // Verify client 2 gets TextView1.
         verify(mMockClientCallback2, times(1))
                 .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
                         eq(mMockClient2InteractionId));
         assertEquals(TEXT_VIEW_1_DESCRIPTION,
                 mFindInfoCaptor.getValue().getContentDescription());
-        // Verify client 2 has TextView2 as a prefetched node
+        // Verify client 2 gets TextView1's siblings and its parent as prefetched nodes.
         verify(mMockClientCallback2, times(1))
                 .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
                         eq(mMockClient2InteractionId));
         List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue();
-        assertEquals(1, prefetchedNode.size());
-        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription());
+        assertEquals(4, prefetchedNode.size());
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNode.get(0).getContentDescription());
+        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(1).getContentDescription());
+        assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNode.get(2).getContentDescription());
+        assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNode.get(3).getContentDescription());
+    }
+
+    /**
+     * Tests a request for 4 nodes using depth first traversal.
+     *     Request 1: Request the root node.
+     *     Request 2: When TextView4 is prefetched, send a request for the root node. Depth first
+     *     traversal completes here.
+     * Out of the 5 descendants, the root frame's 3rd child (TextView3) should not be prefetched,
+     * since this was not reached by the df-traversal.
+     *
+     *   Layout
+     *                        mRootFrameLayout
+     *                /         |        |              \
+     *      mTextView1 mTextView2 mChildFrameLayout  *mTextView3*
+     *                                    |
+     *                                mTextView4
+     * @throws RemoteException
+     */
+    @Test
+    public void testFindRootView_depthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants()
+            throws RemoteException {
+        mTextView4.setAccessibilityDelegate(new View.AccessibilityDelegate(){
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                final long nodeId = AccessibilityNodeInfo.makeNodeId(
+                        mRootFrameLayout.getAccessibilityViewId(),
+                        AccessibilityNodeProvider.HOST_VIEW_ID);
+                // This request is satisfied by first request.
+                sendNodeRequestToController(nodeId, mMockClientCallback2,
+                        mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
+
+            }
+        });
+        // Request for our RootFrameLayout.
+        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST);
+        mInstrumentation.waitForIdleSync();
+
+        // Verify we get RootFrameLayout.
+        verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
+                mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
+        AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+
+        verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
+                mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
+        // Prefetch all the descendants besides TextView3.
+        List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
+        assertEquals(4, prefetchedNodes.size());
+        assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+        assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+        assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
+    }
+
+    /**
+     * Tests a request for 4 nodes using breadth first traversal.
+     *     Request 1: Request the root node
+     *     Request 2: When TextView3 is prefetched, send a request for the root node. Breadth first
+     *     traversal completes here.
+     * Out of the 5 descendants, the child frame's child (TextView4) should not be prefetched, since
+     * this was not reached by the bf-traversal.
+     *   Layout
+     *                        mRootFrameLayout
+     *                /         |        |              \
+     *      mTextView1 mTextView2 mChildFrameLayout  *mTextView3*
+     *                                    |
+     *                                *mTextView4*
+     * @throws RemoteException
+     */
+    @Test
+    public void testFindRootView_breadthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants()
+            throws RemoteException {
+
+        mTextView3.setAccessibilityDelegate(new View.AccessibilityDelegate(){
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                final long nodeId = AccessibilityNodeInfo.makeNodeId(
+                        mRootFrameLayout.getAccessibilityViewId(),
+                        AccessibilityNodeProvider.HOST_VIEW_ID);
+
+                // This request is satisfied by first request.
+                sendNodeRequestToController(nodeId, mMockClientCallback2,
+                        mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
+            }
+        });
+        // Request for our RootFrameLayout.
+        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST);
+        mInstrumentation.waitForIdleSync();
+
+        // Verify we get RootFrameLayout.
+        verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
+                mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
+        AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+
+        verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
+                mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
+        // Prefetch all the descendants besides TextView4.
+        List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
+        assertEquals(4, prefetchedNodes.size());
+        assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+        assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+        assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
+    }
+
+    /**
+     * Tests a request that should not have prefetching interrupted.
+     *     Request 1: Client 1 requests the root node
+     *     Request 2: When the root node is initialized in
+     *     {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)},
+     *     Client 2 requests TextView1's node
+     *
+     * Request 1 is not interrupted during prefetch, and its prefetched node satisfies Request 2.
+     *
+     * @throws RemoteException
+     */
+    @Test
+    public void testFindRootAndTextNodes_withNoInterruptStrategy_shouldSatisfySecondRequest()
+            throws RemoteException {
+        mRootFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate(){
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                final long nodeId = AccessibilityNodeInfo.makeNodeId(
+                        mTextView1.getAccessibilityViewId(),
+                        AccessibilityNodeProvider.HOST_VIEW_ID);
+
+                // TextView1 is prefetched here after the RootFrameLayout is found. Now enqueue a
+                // same-client request for RootFrameLayout.
+                sendNodeRequestToController(nodeId, mMockClientCallback2,
+                        mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID);
+                }
+        });
+
+        // Client 1 request for RootFrameLayout.
+        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID
+                        | FLAG_PREFETCH_UNINTERRUPTIBLE);
+
+        mInstrumentation.waitForIdleSync();
+
+        // When the controller returns the nodes, it clears the sent list. Check immediately since
+        // the captor will be cleared.
+        doAnswer(invocation -> {
+            List<AccessibilityNodeInfo> nodes = invocation.getArgument(0);
+            assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, nodes.get(0).getContentDescription());
+            assertEquals(TEXT_VIEW_2_DESCRIPTION, nodes.get(1).getContentDescription());
+            assertEquals(CHILD_FRAME_DESCRIPTION, nodes.get(2).getContentDescription());
+            assertEquals(TEXT_VIEW_3_DESCRIPTION, nodes.get(3).getContentDescription());
+            assertEquals(TEXT_VIEW_4_DESCRIPTION, nodes.get(4).getContentDescription());
+            return null;
+        }).when(mMockClientCallback1).setFindAccessibilityNodeInfosResult(
+                anyList(), eq(mMockClient1InteractionId));
+
+        verify(mMockClientCallback1, never())
+                .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
+
+        // Verify client 2 gets TextView1.
+        verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult(
+                mFindInfoCaptor.capture(), eq(mMockClient2InteractionId));
+        AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
+        assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
+    }
+
+    /**
+     * Tests a request for root node where a virtual hierarchy is prefetched.
+     *
+     *   Layout
+     *                        mRootFrameLayout
+     *                /         |        |              \
+     *      mTextView1 mTextView2 mChildFrameLayout  *mTextView3*
+     *                                    |
+     *                                *mTextView4*
+     *                                  |         \
+     *                          virtual view 1   virtual view 2
+     *                                 |
+     *                             virtual view 3
+     * @throws RemoteException
+     */
+    @Test
+    public void testFindRootView_withVirtualView()
+            throws RemoteException {
+        mTextView4.setAccessibilityDelegate(new View.AccessibilityDelegate(){
+            @Override
+            public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+                return new AccessibilityNodeProvider() {
+                    @Override
+                    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+                        if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+                            AccessibilityNodeInfo node = new AccessibilityNodeInfo(host);
+                            node.addChild(host, 1);
+                            node.addChild(host, 2);
+                            node.setContentDescription(TEXT_VIEW_4_DESCRIPTION);
+                            return node;
+                        } else if (virtualViewId == 1) {
+                            AccessibilityNodeInfo node = new AccessibilityNodeInfo(
+                                    host, virtualViewId);
+                            node.setParent(host);
+                            node.setContentDescription(VIRTUAL_VIEW_1_DESCRIPTION);
+                            node.addChild(host, 3);
+                            return node;
+                        } else if (virtualViewId == 2 || virtualViewId == 3) {
+                            AccessibilityNodeInfo node = new AccessibilityNodeInfo(
+                                    host, virtualViewId);
+                            node.setParent(host);
+                            node.setContentDescription(virtualViewId == 2
+                                    ? VIRTUAL_VIEW_2_DESCRIPTION
+                                    : VIRTUAL_VIEW_3_DESCRIPTION);
+                            return node;
+                        }
+                        return null;
+                    }
+                };
+            }
+        });
+        // Request for our RootFrameLayout.
+        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
+                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST);
+        mInstrumentation.waitForIdleSync();
+
+        // Verify we get RootFrameLayout.
+        verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
+                mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
+        AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
+        assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
+
+        verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
+                mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
+
+        List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
+        assertEquals(8, prefetchedNodes.size());
+        assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
+        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
+        assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription());
+        assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(3).getContentDescription());
+
+        assertEquals(VIRTUAL_VIEW_1_DESCRIPTION, prefetchedNodes.get(4).getContentDescription());
+        assertEquals(VIRTUAL_VIEW_3_DESCRIPTION, prefetchedNodes.get(5).getContentDescription());
+
+        assertEquals(VIRTUAL_VIEW_2_DESCRIPTION, prefetchedNodes.get(6).getContentDescription());
+        assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(7).getContentDescription());
+
+
     }
 
     private void sendNodeRequestToController(long requestedNodeId,
@@ -536,9 +847,16 @@
     }
 
     private class TestFrameLayout extends FrameLayout {
+        private int mA11yId;
+        private String mContentDescription;
+        ArrayList<View> mChildren;
 
-        TestFrameLayout(Context context) {
+        TestFrameLayout(Context context, int a11yId, String contentDescription,
+                ArrayList<View> children) {
             super(context);
+            mA11yId = a11yId;
+            mContentDescription = contentDescription;
+            mChildren = children;
         }
 
         @Override
@@ -556,14 +874,15 @@
         @Override
         public int getAccessibilityViewId() {
             // static id doesn't reset after tests so return the same one
-            return 0;
+            return mA11yId;
         }
 
         @Override
         public void addChildrenForAccessibility(ArrayList<View> outChildren) {
             // ViewGroup#addChildrenForAccessbility sorting logic will switch these two
-            outChildren.add(mTextView1);
-            outChildren.add(mTextView2);
+            for (View view : mChildren) {
+                outChildren.add(view);
+            }
         }
 
         @Override
@@ -574,13 +893,17 @@
         @Override
         public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(info);
-            info.setContentDescription(FRAME_LAYOUT_DESCRIPTION);
+            info.setContentDescription(mContentDescription);
         }
     }
 
     private class TestTextView extends TextView {
-        TestTextView(Context context) {
+        private int mA11yId;
+        private String mContentDescription;
+        TestTextView(Context context, int a11yId, String contentDescription) {
             super(context);
+            mA11yId = a11yId;
+            mContentDescription = contentDescription;
         }
 
         @Override
@@ -595,7 +918,7 @@
 
         @Override
         public int getAccessibilityViewId() {
-            return 1;
+            return mA11yId;
         }
 
         @Override
@@ -606,39 +929,7 @@
         @Override
         public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(info);
-            info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
-        }
-    }
-
-    private class TestTextView2 extends TextView {
-        TestTextView2(Context context) {
-            super(context);
-        }
-
-        @Override
-        public int getWindowVisibility() {
-            return VISIBLE;
-        }
-
-        @Override
-        public boolean isShown() {
-            return true;
-        }
-
-        @Override
-        public int getAccessibilityViewId() {
-            return 2;
-        }
-
-        @Override
-        public boolean includeForAccessibility() {
-            return true;
-        }
-
-        @Override
-        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-            super.onInitializeAccessibilityNodeInfo(info);
-            info.setContentDescription(TEXT_VIEW_2_DESCRIPTION);
+            info.setContentDescription(mContentDescription);
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index 5746f6f..e6acc90 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -88,27 +88,27 @@
     }
 
     @Test
-    public void testIsAoD() throws RemoteException {
+    public void testIsAod() throws RemoteException {
         mListener.onDozeChanged(true);
-        assertThat(mProvider.isAoD()).isTrue();
+        assertThat(mProvider.isAod()).isTrue();
         mListener.onDozeChanged(false);
-        assertThat(mProvider.isAoD()).isFalse();
+        assertThat(mProvider.isAod()).isFalse();
 
         when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false);
         mListener.onDozeChanged(true);
-        assertThat(mProvider.isAoD()).isFalse();
+        assertThat(mProvider.isAod()).isFalse();
         mListener.onDozeChanged(false);
-        assertThat(mProvider.isAoD()).isFalse();
+        assertThat(mProvider.isAod()).isFalse();
     }
 
     @Test
-    public void testSubscribesToAoD() throws RemoteException {
+    public void testSubscribesToAod() throws RemoteException {
         final List<Boolean> expected = ImmutableList.of(true, false, true, true, false);
         final List<Boolean> actual = new ArrayList<>();
 
         mProvider.subscribe(mOpContext, ctx -> {
             assertThat(ctx).isSameInstanceAs(mOpContext);
-            actual.add(ctx.isAoD);
+            actual.add(ctx.isAod);
         });
 
         for (boolean v : expected) {
@@ -178,7 +178,7 @@
         assertThat(context).isSameInstanceAs(mOpContext);
         assertThat(mOpContext.id).isEqualTo(0);
         assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
-        assertThat(mOpContext.isAoD).isEqualTo(false);
+        assertThat(mOpContext.isAod).isEqualTo(false);
         assertThat(mOpContext.isCrypto).isEqualTo(false);
 
         for (int type : List.of(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
@@ -192,7 +192,7 @@
             assertThat(context).isSameInstanceAs(mOpContext);
             assertThat(mOpContext.id).isEqualTo(id);
             assertThat(mOpContext.reason).isEqualTo(reason(type));
-            assertThat(mOpContext.isAoD).isEqualTo(aod);
+            assertThat(mOpContext.isAod).isEqualTo(aod);
             assertThat(mOpContext.isCrypto).isEqualTo(false);
 
             mSessionListener.onSessionEnded(type, InstanceId.fakeInstanceId(id));
@@ -202,7 +202,7 @@
         assertThat(context).isSameInstanceAs(mOpContext);
         assertThat(mOpContext.id).isEqualTo(0);
         assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
-        assertThat(mOpContext.isAoD).isEqualTo(false);
+        assertThat(mOpContext.isAod).isEqualTo(false);
         assertThat(mOpContext.isCrypto).isEqualTo(false);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 3b4aece..2ae2854 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.companion.virtual;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -39,6 +40,7 @@
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.audio.IAudioSessionCallback;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.graphics.Point;
@@ -111,6 +113,8 @@
     @Mock
     IThermalService mIThermalServiceMock;
     private PowerManager mPowerManager;
+    @Mock
+    private IAudioSessionCallback mCallback;
 
     @Before
     public void setUp() {
@@ -250,6 +254,12 @@
     }
 
     @Test
+    public void onAudioSessionStarting_noDisplay_failsSecurityException() {
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback));
+    }
+
+    @Test
     public void createVirtualKeyboard_noPermission_failsSecurityException() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
@@ -283,6 +293,22 @@
     }
 
     @Test
+    public void onAudioSessionStarting_noPermission_failsSecurityException() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback));
+    }
+
+    @Test
+    public void onAudioSessionEnded_noPermission_failsSecurityException() {
+        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
+    }
+
+    @Test
     public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
@@ -316,6 +342,25 @@
     }
 
     @Test
+    public void onAudioSessionStarting_hasVirtualAudioController() {
+        mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+
+        mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback);
+
+        assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNotNull();
+    }
+
+    @Test
+    public void onAudioSessionEnded_noVirtualAudioController() {
+        mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+        mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback);
+
+        mDeviceImpl.onAudioSessionEnded();
+
+        assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull();
+    }
+
+    @Test
     public void sendKeyEvent_noFd() {
         assertThrows(
                 IllegalArgumentException.class,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
new file mode 100644
index 0000000..3160272
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -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.server.companion.virtual.audio;
+
+import static android.media.AudioAttributes.FLAG_SECURE;
+import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.companion.virtual.audio.IAudioSessionCallback;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
+import android.media.MediaRecorder;
+import android.media.PlayerBase;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.companion.virtual.GenericWindowPolicyController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualAudioControllerTest {
+    private static final int APP1_UID = 100;
+    private static final int APP2_UID = 200;
+
+    private Context mContext;
+    private VirtualAudioController mVirtualAudioController;
+    private GenericWindowPolicyController mGenericWindowPolicyController;
+    @Mock IAudioSessionCallback mCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        mVirtualAudioController = new VirtualAudioController(mContext);
+        mGenericWindowPolicyController = new GenericWindowPolicyController(
+                FLAG_SECURE,
+                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                /* allowedUsers= */ new ArraySet<>(),
+                /* allowedActivities= */ new ArraySet<>(),
+                /* blockedActivities= */ new ArraySet<>(),
+                /* activityListener= */null,
+                /* activityBlockedCallback= */ null);
+    }
+
+    @Test
+    public void startListening_receivesCallback() throws RemoteException {
+        ArraySet<Integer> runningUids = new ArraySet<>();
+        runningUids.add(APP1_UID);
+        int[] appUids = new int[] {APP1_UID};
+
+        mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
+
+        mGenericWindowPolicyController.onRunningAppsChanged(runningUids);
+        verify(mCallback).onAppsNeedingAudioRoutingChanged(appUids);
+    }
+
+    @Test
+    public void stopListening_removesCallback() throws RemoteException {
+        ArraySet<Integer> runningUids = new ArraySet<>();
+        runningUids.add(APP1_UID);
+        int[] appUids = new int[] {APP1_UID};
+        mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
+
+        mVirtualAudioController.stopListening();
+
+        mGenericWindowPolicyController.onRunningAppsChanged(runningUids);
+        verify(mCallback, never()).onAppsNeedingAudioRoutingChanged(appUids);
+    }
+
+    @Test
+    public void onRunningAppsChanged_notifiesAudioRoutingModified() throws RemoteException {
+        mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
+
+        ArraySet<Integer> runningUids = new ArraySet<>();
+        runningUids.add(APP1_UID);
+        mVirtualAudioController.onRunningAppsChanged(runningUids);
+
+        int[] appUids = new int[] {APP1_UID};
+        verify(mCallback).onAppsNeedingAudioRoutingChanged(appUids);
+    }
+
+    @Test
+    public void onRunningAppsChanged_audioIsPlaying_doesNothing() throws RemoteException {
+        mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
+        mVirtualAudioController.addPlayingAppsForTesting(APP2_UID);
+
+        ArraySet<Integer> runningUids = new ArraySet<>();
+        runningUids.add(APP1_UID);
+        mVirtualAudioController.onRunningAppsChanged(runningUids);
+
+        int[] appUids = new int[]{APP1_UID};
+        verify(mCallback, never()).onAppsNeedingAudioRoutingChanged(appUids);
+    }
+
+    @Test
+    public void onRunningAppsChanged_lastPlayingAppRemoved_delaysReroutingAudio() {
+        ArraySet<Integer> runningUids = new ArraySet<>();
+        runningUids.add(APP1_UID);
+        runningUids.add(APP2_UID);
+        mVirtualAudioController.onRunningAppsChanged(runningUids);
+        mVirtualAudioController.addPlayingAppsForTesting(APP2_UID);
+
+        ArraySet<Integer> appUids = new ArraySet<>();
+        appUids.add(APP1_UID);
+        mVirtualAudioController.onRunningAppsChanged(appUids);
+
+        assertThat(mVirtualAudioController.hasPendingRunnable()).isTrue();
+    }
+
+    @Test
+    public void onPlaybackConfigChanged_sendsCallback() throws RemoteException {
+        mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
+        ArraySet<Integer> runningUids = new ArraySet<>();
+        runningUids.add(APP1_UID);
+        mVirtualAudioController.onRunningAppsChanged(runningUids);
+        List<AudioPlaybackConfiguration> configs = createPlaybackConfigurations(runningUids);
+
+        mVirtualAudioController.onPlaybackConfigChanged(configs);
+
+        verify(mCallback).onPlaybackConfigChanged(configs);
+    }
+
+    @Test
+    public void onRecordingConfigChanged_sendsCallback() throws RemoteException {
+        mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
+        ArraySet<Integer> runningUids = new ArraySet<>();
+        runningUids.add(APP1_UID);
+        mVirtualAudioController.onRunningAppsChanged(runningUids);
+        List<AudioRecordingConfiguration> configs = createRecordingConfigurations(runningUids);
+
+        mVirtualAudioController.onRecordingConfigChanged(configs);
+
+        verify(mCallback).onRecordingConfigChanged(configs);
+    }
+
+    private List<AudioPlaybackConfiguration> createPlaybackConfigurations(
+            ArraySet<Integer> appUids) {
+        List<AudioPlaybackConfiguration> configs = new ArrayList<>();
+        for (int appUid : appUids) {
+            PlayerBase.PlayerIdCard playerIdCard =
+                    PlayerBase.PlayerIdCard.CREATOR.createFromParcel(Parcel.obtain());
+            AudioPlaybackConfiguration audioPlaybackConfiguration =
+                    new AudioPlaybackConfiguration(
+                            playerIdCard, /* piid= */ 1000, appUid, /* pid= */ 1000);
+            audioPlaybackConfiguration.handleStateEvent(PLAYER_STATE_STARTED, /* deviceId= */1);
+            configs.add(audioPlaybackConfiguration);
+        }
+        return configs;
+    }
+
+    private List<AudioRecordingConfiguration> createRecordingConfigurations(
+            ArraySet<Integer> appUids) {
+        List<AudioRecordingConfiguration> configs = new ArrayList<>();
+        for (int appUid : appUids) {
+            AudioRecordingConfiguration audioRecordingConfiguration =
+                    new AudioRecordingConfiguration(
+                            /* uid= */ appUid,
+                            /* session= */ 1000,
+                            MediaRecorder.AudioSource.MIC,
+                            /* clientFormat= */ null,
+                            /* devFormat= */ null,
+                            /* patchHandle= */ 1000,
+                            "com.android.example");
+            configs.add(audioRecordingConfiguration);
+        }
+        return configs;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java
new file mode 100644
index 0000000..74d2e0f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.service.dreams.DreamService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamServiceTest {
+    @Test
+    public void testMetadataParsing() throws PackageManager.NameNotFoundException {
+        final String testPackageName = "com.android.frameworks.servicestests";
+        final String testDreamClassName = "com.android.server.dreams.TestDreamService";
+        final String testSettingsActivity = "com.android.server.dreams/.TestDreamSettingsActivity";
+
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        final ServiceInfo si = context.getPackageManager().getServiceInfo(
+                new ComponentName(testPackageName, testDreamClassName),
+                PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
+        final DreamService.DreamMetadata metadata = DreamService.getDreamMetadata(context, si);
+
+        assertEquals(0, metadata.settingsActivity.compareTo(
+                ComponentName.unflattenFromString(testSettingsActivity)));
+        assertFalse(metadata.showComplications);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java b/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java
new file mode 100644
index 0000000..3c99a98
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import android.service.dreams.DreamService;
+
+/**
+ * Dream service implementation for unit testing.
+ */
+public class TestDreamService extends DreamService {
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index f26e094..096c80b 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -142,6 +142,7 @@
                 new AppInstallMetadata.Builder()
                         .setPackageName(packageName)
                         .setAppCertificates(Collections.singletonList(packageCert))
+                        .setAppCertificateLineage(Collections.singletonList(packageCert))
                         .setVersionCode(version)
                         .setInstallerName("abc")
                         .setInstallerCertificates(Collections.singletonList("abc"))
@@ -183,6 +184,8 @@
                 new AppInstallMetadata.Builder()
                         .setPackageName(installedPackageName)
                         .setAppCertificates(Collections.singletonList(installedAppCertificate))
+                        .setAppCertificateLineage(
+                                Collections.singletonList(installedAppCertificate))
                         .setVersionCode(250)
                         .setInstallerName("abc")
                         .setInstallerCertificates(Collections.singletonList("abc"))
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
index 441cd4b..1c860ca 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
@@ -183,6 +183,7 @@
         return new AppInstallMetadata.Builder()
                 .setPackageName("abc")
                 .setAppCertificates(Collections.singletonList("abc"))
+                .setAppCertificateLineage(Collections.singletonList("abc"))
                 .setInstallerCertificates(Collections.singletonList("abc"))
                 .setInstallerName("abc")
                 .setVersionCode(-1)
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index b271a77..5089f74 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -49,6 +49,7 @@
             new AppInstallMetadata.Builder()
                     .setPackageName(PACKAGE_NAME_1)
                     .setAppCertificates(Collections.singletonList(APP_CERTIFICATE))
+                    .setAppCertificateLineage(Collections.singletonList(APP_CERTIFICATE))
                     .setVersionCode(2)
                     .build();
 
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
index 415e635..370bd80 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -54,6 +54,7 @@
                 new AppInstallMetadata.Builder()
                         .setPackageName("ddd")
                         .setAppCertificates(Collections.singletonList("777"))
+                        .setAppCertificateLineage(Collections.singletonList("777"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
@@ -76,6 +77,7 @@
                 new AppInstallMetadata.Builder()
                         .setPackageName("ddd")
                         .setAppCertificates(Arrays.asList("777", "999"))
+                        .setAppCertificateLineage(Arrays.asList("777", "999"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
@@ -99,6 +101,7 @@
                 new AppInstallMetadata.Builder()
                         .setPackageName("bbb")
                         .setAppCertificates(Collections.singletonList("999"))
+                        .setAppCertificateLineage(Collections.singletonList("999"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
@@ -121,6 +124,7 @@
                 new AppInstallMetadata.Builder()
                         .setPackageName("ccc")
                         .setAppCertificates(Collections.singletonList("444"))
+                        .setAppCertificateLineage(Collections.singletonList("444"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
@@ -153,6 +157,7 @@
                 new AppInstallMetadata.Builder()
                         .setPackageName("ccc")
                         .setAppCertificates(Collections.singletonList("444"))
+                        .setAppCertificateLineage(Collections.singletonList("444"))
                         .build();
 
         List<RuleIndexRange> resultingIndexes =
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index b878779..9ed2e88 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -875,6 +875,11 @@
             }
 
             @Override
+            public boolean isAppCertificateLineageFormula() {
+                return false;
+            }
+
+            @Override
             public boolean isInstallerFormula() {
                 return false;
             }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index ea9e6ff..6dccdf5 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -305,6 +305,11 @@
             }
 
             @Override
+            public boolean isAppCertificateLineageFormula() {
+                return false;
+            }
+
+            @Override
             public boolean isInstallerFormula() {
                 return false;
             }
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index f865a50..af8ac6f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -23,11 +23,13 @@
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -38,8 +40,10 @@
 import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -200,6 +204,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -1877,55 +1882,99 @@
     }
 
     @Test
+    public void testLowPowerStandbyAllowlist() throws Exception {
+        callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP, 0);
+        callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+        callOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+        expectHasInternetPermission(UID_A, true);
+        expectHasInternetPermission(UID_B, true);
+        expectHasInternetPermission(UID_C, true);
+
+        final NetworkPolicyManagerInternal internal = LocalServices
+                .getService(NetworkPolicyManagerInternal.class);
+
+        Map<Integer, Integer> firewallUidRules = new ArrayMap<>();
+        doAnswer(arg -> {
+            int[] uids = arg.getArgument(1);
+            int[] rules = arg.getArgument(2);
+            assertTrue(uids.length == rules.length);
+
+            for (int i = 0; i < uids.length; ++i) {
+                firewallUidRules.put(uids[i], rules[i]);
+            }
+            return null;
+        }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_LOW_POWER_STANDBY),
+                any(int[].class), any(int[].class));
+
+        internal.setLowPowerStandbyAllowlist(new int[] { UID_B });
+        internal.setLowPowerStandbyActive(true);
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A).intValue());
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_B).intValue());
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+        assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+        assertTrue(mService.isUidNetworkingBlocked(UID_C, false));
+
+        internal.setLowPowerStandbyActive(false);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+        assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+        assertFalse(mService.isUidNetworkingBlocked(UID_C, false));
+    }
+
+    @Test
     public void testUpdateEffectiveBlockedReasons() {
-        final SparseArray<Pair<Integer, Integer>> effectiveBlockedReasons = new SparseArray<>();
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_REASON_NONE, ALLOWED_REASON_NONE));
+        final Map<Pair<Integer, Integer>, Integer> effectiveBlockedReasons = new HashMap<>();
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_NONE, ALLOWED_REASON_NONE),
+                BLOCKED_REASON_NONE);
 
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_SYSTEM));
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE,
-                        ALLOWED_REASON_SYSTEM));
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_METERED_REASON_DATA_SAVER,
-                        ALLOWED_METERED_REASON_SYSTEM));
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_METERED_REASON_DATA_SAVER
-                                | BLOCKED_METERED_REASON_USER_RESTRICTED,
-                        ALLOWED_METERED_REASON_SYSTEM));
+        effectiveBlockedReasons.put(
+                Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_SYSTEM),
+                BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE,
+                ALLOWED_REASON_SYSTEM), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(
+                Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_SYSTEM),
+                BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_METERED_REASON_DATA_SAVER
+                        | BLOCKED_METERED_REASON_USER_RESTRICTED,
+                ALLOWED_METERED_REASON_SYSTEM), BLOCKED_REASON_NONE);
 
-        effectiveBlockedReasons.put(BLOCKED_METERED_REASON_DATA_SAVER,
+        effectiveBlockedReasons.put(
                 Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_METERED_REASON_DATA_SAVER,
-                        ALLOWED_REASON_SYSTEM));
-        effectiveBlockedReasons.put(BLOCKED_REASON_APP_STANDBY,
+                        ALLOWED_REASON_SYSTEM), BLOCKED_METERED_REASON_DATA_SAVER);
+        effectiveBlockedReasons.put(
                 Pair.create(BLOCKED_REASON_APP_STANDBY | BLOCKED_METERED_REASON_USER_RESTRICTED,
-                        ALLOWED_METERED_REASON_SYSTEM));
+                        ALLOWED_METERED_REASON_SYSTEM), BLOCKED_REASON_APP_STANDBY);
 
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_FOREGROUND));
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE,
-                        ALLOWED_REASON_FOREGROUND));
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_FOREGROUND));
-        effectiveBlockedReasons.put(BLOCKED_REASON_NONE,
-                Pair.create(BLOCKED_METERED_REASON_DATA_SAVER
-                                | BLOCKED_METERED_REASON_USER_RESTRICTED,
-                        ALLOWED_METERED_REASON_FOREGROUND));
-        effectiveBlockedReasons.put(BLOCKED_METERED_REASON_DATA_SAVER,
+        effectiveBlockedReasons.put(
+                Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_FOREGROUND),
+                BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE,
+                ALLOWED_REASON_FOREGROUND), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(
+                Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_FOREGROUND),
+                BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_METERED_REASON_DATA_SAVER
+                        | BLOCKED_METERED_REASON_USER_RESTRICTED,
+                ALLOWED_METERED_REASON_FOREGROUND), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(
                 Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_METERED_REASON_DATA_SAVER,
-                        ALLOWED_REASON_FOREGROUND));
-        effectiveBlockedReasons.put(BLOCKED_REASON_BATTERY_SAVER,
-                Pair.create(BLOCKED_REASON_BATTERY_SAVER
-                                | BLOCKED_METERED_REASON_USER_RESTRICTED,
-                        ALLOWED_METERED_REASON_FOREGROUND));
+                        ALLOWED_REASON_FOREGROUND), BLOCKED_METERED_REASON_DATA_SAVER);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER
+                        | BLOCKED_METERED_REASON_USER_RESTRICTED,
+                ALLOWED_METERED_REASON_FOREGROUND), BLOCKED_REASON_BATTERY_SAVER);
+
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
+                ALLOWED_REASON_FOREGROUND), BLOCKED_REASON_LOW_POWER_STANDBY);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
+                ALLOWED_REASON_TOP), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
+                ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST), BLOCKED_REASON_NONE);
         // TODO: test more combinations of blocked reasons.
 
-        for (int i = 0; i < effectiveBlockedReasons.size(); ++i) {
-            final int expectedEffectiveBlockedReasons = effectiveBlockedReasons.keyAt(i);
-            final int blockedReasons = effectiveBlockedReasons.valueAt(i).first;
-            final int allowedReasons = effectiveBlockedReasons.valueAt(i).second;
+        for (Map.Entry<Pair<Integer, Integer>, Integer> test : effectiveBlockedReasons.entrySet()) {
+            final int expectedEffectiveBlockedReasons = test.getValue();
+            final int blockedReasons = test.getKey().first;
+            final int allowedReasons = test.getKey().second;
             final String errorMsg = "Expected="
                     + blockedReasonsToString(expectedEffectiveBlockedReasons)
                     + "; blockedReasons=" + blockedReasonsToString(blockedReasons)
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 31bdec1..952200d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -895,6 +895,7 @@
         assertEquals(a.networkSecurityConfigRes, that.networkSecurityConfigRes);
         assertEquals(a.taskAffinity, that.taskAffinity);
         assertEquals(a.permission, that.permission);
+        assertEquals(a.getKnownActivityEmbeddingCerts(), that.getKnownActivityEmbeddingCerts());
         assertEquals(a.processName, that.processName);
         assertEquals(a.className, that.className);
         assertEquals(a.manageSpaceActivityName, that.manageSpaceActivityName);
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
index 4ae9613..00a7944 100644
--- a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -47,6 +47,7 @@
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
+import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.testutils.OffsettableClock;
 
 import org.junit.After;
@@ -80,6 +81,8 @@
     private IPowerManager mIPowerManagerMock;
     @Mock
     private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock
+    private NetworkPolicyManagerInternal mNetworkPolicyManagerInternal;
 
     @Before
     public void setUp() throws Exception {
@@ -90,6 +93,7 @@
         PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null);
         when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
         addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+        addLocalServiceMock(NetworkPolicyManagerInternal.class, mNetworkPolicyManagerInternal);
 
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
@@ -121,6 +125,7 @@
     public void tearDown() throws Exception {
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
         LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
+        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
     }
 
     @Test
@@ -130,6 +135,7 @@
 
         assertThat(mController.isActive()).isFalse();
         verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+        verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean());
     }
 
     @Test
@@ -142,6 +148,7 @@
         awaitStandbyTimeoutAlarm();
         assertThat(mController.isActive()).isTrue();
         verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+        verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(true);
     }
 
     private void awaitStandbyTimeoutAlarm() {
@@ -169,6 +176,7 @@
 
         assertThat(mController.isActive()).isFalse();
         verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+        verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean());
     }
 
     @Test
@@ -182,6 +190,7 @@
 
         assertThat(mController.isActive()).isTrue();
         verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+        verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(true);
     }
 
     @Test
@@ -197,6 +206,7 @@
 
         assertThat(mController.isActive()).isFalse();
         verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+        verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean());
     }
 
     private void verifyStandbyAlarmCancelled() {
@@ -221,6 +231,7 @@
 
         assertThat(mController.isActive()).isFalse();
         verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+        verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(false);
     }
 
     @Test
@@ -238,6 +249,7 @@
 
         assertThat(mController.isActive()).isFalse();
         verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+        verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(false);
     }
 
     @Test
@@ -255,6 +267,7 @@
 
         assertThat(mController.isActive()).isTrue();
         verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false);
+        verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(false);
     }
 
     @Test
@@ -273,6 +286,7 @@
 
         assertThat(mController.isActive()).isTrue();
         verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true);
+        verify(mNetworkPolicyManagerInternal, times(2)).setLowPowerStandbyActive(true);
     }
 
     @Test
@@ -285,6 +299,7 @@
         assertThat(mController.isActive()).isFalse();
         verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any());
         verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+        verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean());
     }
 
     @Test
@@ -350,10 +365,12 @@
         service.addToAllowlist(10);
         mTestLooper.dispatchAll();
         verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {10});
+        verify(mNetworkPolicyManagerInternal).setLowPowerStandbyAllowlist(new int[] {10});
 
         service.removeFromAllowlist(10);
         mTestLooper.dispatchAll();
         verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {});
+        verify(mNetworkPolicyManagerInternal).setLowPowerStandbyAllowlist(new int[] {});
     }
 
     @Test
@@ -366,12 +383,14 @@
 
         assertThat(mController.isActive()).isTrue();
         verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true);
+        verify(mNetworkPolicyManagerInternal).setLowPowerStandbyActive(true);
 
         mController.forceActive(false);
         mTestLooper.dispatchAll();
 
         assertThat(mController.isActive()).isFalse();
         verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false);
+        verify(mNetworkPolicyManagerInternal).setLowPowerStandbyActive(false);
     }
 
     private void setLowPowerStandbySupportedConfig(boolean supported) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ef9494a..dfcab2b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -102,6 +102,7 @@
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
@@ -241,6 +242,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
 @RunWithLooper
 public class NotificationManagerServiceTest extends UiServiceTestCase {
     private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
@@ -1338,7 +1340,8 @@
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -1359,7 +1362,8 @@
         when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(IMPORTANCE_NONE);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -3921,7 +3925,8 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -3938,7 +3943,8 @@
         r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -3954,7 +3960,8 @@
         mService.addEnqueuedNotification(r);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -3967,12 +3974,14 @@
         r.setCriticality(CriticalNotificationExtractor.CRITICAL_LOW);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
 
         r = generateNotificationRecord(mTestNotificationChannel, 1, null, false);
         r.setCriticality(CriticalNotificationExtractor.CRITICAL);
-        runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+        runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                r.getUid(), SystemClock.elapsedRealtime());
         mService.addEnqueuedNotification(r);
 
         runnable.run();
@@ -4006,6 +4015,63 @@
     }
 
     @Test
+    public void testMediaStyleRemote_hasPermission() throws RemoteException {
+        String deviceName = "device";
+        when(mPackageManager.checkPermission(
+                eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
+                .thenReturn(PERMISSION_GRANTED);
+        Notification.MediaStyle style = new Notification.MediaStyle();
+        style.setRemotePlaybackInfo(deviceName, 0, null);
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setStyle(style);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testMediaStyleRemoteHasPermission", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        NotificationRecord posted = mService.findNotificationLocked(
+                PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+        Bundle extras = posted.getNotification().extras;
+
+        assertTrue(extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
+        assertEquals(deviceName, extras.getString(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
+    }
+
+    @Test
+    public void testMediaStyleRemote_noPermission() throws RemoteException {
+        String deviceName = "device";
+        when(mPackageManager.checkPermission(
+                eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
+                .thenReturn(PERMISSION_DENIED);
+        Notification.MediaStyle style = new Notification.MediaStyle();
+        style.setRemotePlaybackInfo(deviceName, 0, null);
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setStyle(style);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testMediaStyleRemoteNoPermission", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        NotificationRecord posted = mService.findNotificationLocked(
+                PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+        assertFalse(posted.getNotification().extras
+                .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
+    }
+
+    @Test
     public void testGetNotificationCountLocked() {
         String sampleTagToExclude = null;
         int sampleIdToExclude = 0;
@@ -4416,6 +4482,8 @@
 
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(original.getKey(),
+                        original.getSbn().getPackageName(),
+                        original.getUid(),
                         SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
@@ -4438,6 +4506,8 @@
 
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(),
+                        update.getUid(),
                         SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
@@ -6533,7 +6603,8 @@
         assertNull(update.getSbn().getNotification().getSmallIcon());
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(update.getKey(),
+                mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(),
                         SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index fec5405..d922f40 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -657,7 +657,8 @@
         when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -790,7 +791,8 @@
 
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -806,7 +808,8 @@
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mService.addEnqueuedNotification(r);
-        runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+        runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -822,7 +825,8 @@
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mService.addEnqueuedNotification(r);
-        runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
+        runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                r.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 3b67182..a3440b4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -233,8 +233,8 @@
     public void testSetNotificationPermission_grantReviewRequired() throws Exception {
         mPermissionHelper.setNotificationPermission("pkg", 10, true, false, true);
 
-        verify(mPermManager).grantRuntimePermission(
-                "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+        verify(mPermManager).revokeRuntimePermission(
+                "pkg", Manifest.permission.POST_NOTIFICATIONS, 10, "PermissionHelper");
         verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
                 FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10);
     }
@@ -245,8 +245,8 @@
                 "pkg", 10, true, false);
         mPermissionHelper.setNotificationPermission(pkgPerm);
 
-        verify(mPermManager).grantRuntimePermission(
-                "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+        verify(mPermManager).revokeRuntimePermission(
+                "pkg", Manifest.permission.POST_NOTIFICATIONS, 10, "PermissionHelper");
         verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
                 FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10);
     }
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 3298d11..043bc07 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -121,6 +121,7 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PersistableBundle;
@@ -3358,6 +3359,29 @@
                 noProcActivity.mInputDispatchingTimeoutMillis);
     }
 
+    @Test
+    public void testEnsureActivitiesVisibleAnotherUserTasks() {
+        // Create an activity with hierarchy:
+        //    RootTask
+        //       - TaskFragment
+        //          - Activity
+        DisplayContent display = createNewDisplay();
+        Task rootTask = createTask(display);
+        ActivityRecord activity = createActivityRecord(rootTask);
+        final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(),
+                true /* createdByOrganizer */, true /* isEmbedded */);
+        activity.getTask().addChild(taskFragment, POSITION_TOP);
+        activity.reparent(taskFragment, POSITION_TOP);
+
+        // Ensure the activity visibility is updated even it is not shown to current user.
+        activity.mVisibleRequested = true;
+        doReturn(false).when(activity).showToCurrentUser();
+        spyOn(taskFragment);
+        doReturn(false).when(taskFragment).shouldBeVisible(any());
+        display.ensureActivitiesVisible(null, 0, false, false);
+        assertFalse(activity.mVisibleRequested);
+    }
+
     private ICompatCameraControlCallback getCompatCameraControlCallback() {
         return new ICompatCameraControlCallback.Stub() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index c58bf3b..c8e48a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -75,6 +75,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.SigningDetails;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
@@ -84,9 +85,11 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.Pair;
 import android.view.Gravity;
+import android.window.TaskFragmentOrganizerToken;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
 import com.android.server.wm.utils.MockTracker;
 
@@ -94,6 +97,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Tests for the {@link ActivityStarter} class.
  *
@@ -1109,7 +1116,7 @@
     }
 
     @Test
-    public void testStartActivityInner_inTaskFragment() {
+    public void testStartActivityInner_inTaskFragment_failsByDefault() {
         final ActivityStarter starter = prepareStarter(0, false);
         final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
         final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -1130,6 +1137,97 @@
                 /* restrictedBgActivity */false,
                 /* intentGrants */null);
 
+        assertFalse(taskFragment.hasChild());
+    }
+
+    @Test
+    public void testStartActivityInner_inTaskFragment_allowedForSameUid() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token,
+                true /* createdByOrganizer */);
+        sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
+
+        taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class),
+                targetRecord.getUid(), "test_process_name");
+
+        starter.startActivityInner(
+                /* r */targetRecord,
+                /* sourceRecord */ sourceRecord,
+                /* voiceSession */null,
+                /* voiceInteractor */ null,
+                /* startFlags */ 0,
+                /* doResume */true,
+                /* options */null,
+                /* inTask */null,
+                /* inTaskFragment */ taskFragment,
+                /* restrictedBgActivity */false,
+                /* intentGrants */null);
+
+        assertTrue(taskFragment.hasChild());
+    }
+
+    @Test
+    public void testStartActivityInner_inTaskFragment_allowedTrustedCertUid() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token,
+                true /* createdByOrganizer */);
+        sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
+
+        taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class),
+                12345, "test_process_name");
+        AndroidPackage androidPackage = mock(AndroidPackage.class);
+        doReturn(androidPackage).when(mMockPackageManager).getPackage(eq(12345));
+
+        Set<String> certs = new HashSet(Arrays.asList("test_cert1", "test_cert1"));
+        targetRecord.info.setKnownActivityEmbeddingCerts(certs);
+        SigningDetails signingDetails = mock(SigningDetails.class);
+        doReturn(true).when(signingDetails).hasAncestorOrSelfWithDigest(any());
+        doReturn(signingDetails).when(androidPackage).getSigningDetails();
+
+        starter.startActivityInner(
+                /* r */targetRecord,
+                /* sourceRecord */ sourceRecord,
+                /* voiceSession */null,
+                /* voiceInteractor */ null,
+                /* startFlags */ 0,
+                /* doResume */true,
+                /* options */null,
+                /* inTask */null,
+                /* inTaskFragment */ taskFragment,
+                /* restrictedBgActivity */false,
+                /* intentGrants */null);
+
+        assertTrue(taskFragment.hasChild());
+    }
+
+    @Test
+    public void testStartActivityInner_inTaskFragment_allowedForUntrustedEmbedding() {
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token,
+                true /* createdByOrganizer */);
+        sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
+
+        targetRecord.info.flags |= ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
+
+        starter.startActivityInner(
+                /* r */targetRecord,
+                /* sourceRecord */ sourceRecord,
+                /* voiceSession */null,
+                /* voiceInteractor */ null,
+                /* startFlags */ 0,
+                /* doResume */true,
+                /* options */null,
+                /* inTask */null,
+                /* inTaskFragment */ taskFragment,
+                /* restrictedBgActivity */false,
+                /* intentGrants */null);
+
         assertTrue(taskFragment.hasChild());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index efc9a49..9f7130e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -447,25 +447,6 @@
     }
 
     /**
-     * This tests override configuration updates for display content.
-     */
-    @Test
-    public void testDisplayOverrideConfigUpdate() {
-        final Configuration currentOverrideConfig =
-                mDisplayContent.getRequestedOverrideConfiguration();
-
-        // Create new, slightly changed override configuration and apply it to the display.
-        final Configuration newOverrideConfig = new Configuration(currentOverrideConfig);
-        newOverrideConfig.densityDpi += 120;
-        newOverrideConfig.fontScale += 0.3;
-
-        mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, mDisplayContent);
-
-        // Check that override config is applied.
-        assertEquals(newOverrideConfig, mDisplayContent.getRequestedOverrideConfiguration());
-    }
-
-    /**
      * This tests global configuration updates when default display config is updated.
      */
     @Test
@@ -478,7 +459,8 @@
         newOverrideConfig.densityDpi += 120;
         newOverrideConfig.fontScale += 0.3;
 
-        mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, defaultDisplay);
+        defaultDisplay.updateDisplayOverrideConfigurationLocked(newOverrideConfig,
+                null /* starting */, false /* deferResume */, null /* result */);
 
         // Check that global configuration is updated, as we've updated default display's config.
         Configuration globalConfig = mWm.mRoot.getConfiguration();
@@ -486,7 +468,8 @@
         assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
 
         // Return back to original values.
-        mWm.setNewDisplayOverrideConfiguration(currentConfig, defaultDisplay);
+        defaultDisplay.updateDisplayOverrideConfigurationLocked(currentConfig,
+                null /* starting */, false /* deferResume */, null /* result */);
         globalConfig = mWm.mRoot.getConfiguration();
         assertEquals(currentConfig.densityDpi, globalConfig.densityDpi);
         assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 87f76fa..e091190 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -16,9 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -632,10 +629,11 @@
     @Test
     public void testAttachNavBarInSplitScreenMode() {
         setupForShouldAttachNavBarDuringTransition();
-        final ActivityRecord primary = createActivityRecordWithParentTask(mDefaultDisplay,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        final ActivityRecord secondary = createActivityRecordWithParentTask(mDefaultDisplay,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
+        final ActivityRecord primary = createActivityRecordWithParentTask(
+                organizer.createTaskToPrimary(true));
+        final ActivityRecord secondary = createActivityRecordWithParentTask(
+                organizer.createTaskToSecondary(true));
         final ActivityRecord homeActivity = createHomeActivity();
         homeActivity.setVisibility(true);
         initializeRecentsAnimationController(mController, homeActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 4095728..05eedcf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -24,6 +24,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.os.Process.SYSTEM_UID;
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
@@ -1531,7 +1532,11 @@
 
             final Rect primaryBounds = new Rect();
             final Rect secondaryBounds = new Rect();
-            display.getBounds().splitVertically(primaryBounds, secondaryBounds);
+            if (display.getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+                display.getBounds().splitVertically(primaryBounds, secondaryBounds);
+            } else {
+                display.getBounds().splitHorizontally(primaryBounds, secondaryBounds);
+            }
             mPrimary.setBounds(primaryBounds);
             mSecondary.setBounds(secondaryBounds);
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 8acd3c7..84bd983 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -45,7 +45,6 @@
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SharedMemory;
@@ -278,8 +277,10 @@
         mRemoteHotwordDetectionService.unbind();
         LocalServices.getService(PermissionManagerServiceInternal.class)
                 .setHotwordDetectionServiceProvider(null);
+        if (mIdentity != null) {
+            removeServiceUidForAudioPolicy(mIdentity.getIsolatedUid());
+        }
         mIdentity = null;
-        updateServiceUidForAudioPolicy(Process.INVALID_UID);
         mCancellationTaskFuture.cancel(/* may interrupt */ true);
         if (mAudioFlinger != null) {
             mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
@@ -909,17 +910,27 @@
                 LocalServices.getService(PermissionManagerServiceInternal.class)
                         .setHotwordDetectionServiceProvider(() -> uid);
                 mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
-                updateServiceUidForAudioPolicy(uid);
+                addServiceUidForAudioPolicy(uid);
             }
         }));
     }
 
-    private void updateServiceUidForAudioPolicy(int uid) {
+    private void addServiceUidForAudioPolicy(int uid) {
         mScheduledExecutorService.execute(() -> {
-            final AudioManagerInternal audioManager =
+            AudioManagerInternal audioManager =
                     LocalServices.getService(AudioManagerInternal.class);
             if (audioManager != null) {
-                audioManager.setHotwordDetectionServiceUid(uid);
+                audioManager.addAssistantServiceUid(uid);
+            }
+        });
+    }
+
+    private void removeServiceUidForAudioPolicy(int uid) {
+        mScheduledExecutorService.execute(() -> {
+            AudioManagerInternal audioManager =
+                    LocalServices.getService(AudioManagerInternal.class);
+            if (audioManager != null) {
+                audioManager.removeAssistantServiceUid(uid);
             }
         });
     }
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 30ed7c2..8d92520 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -524,7 +524,10 @@
      *     defined in {@code RESOLVABLE_ERROR_}. A subclass should override this method. Otherwise,
      *     this method does nothing and returns null by default.
      * @see android.telephony.euicc.EuiccManager#downloadSubscription
+     * @deprecated prefer {@link #onDownloadSubscription(int, int,
+     *     DownloadableSubscription, boolean, boolean, Bundle)}
      */
+    @Deprecated
     public DownloadSubscriptionResult onDownloadSubscription(int slotId,
             @NonNull DownloadableSubscription subscription, boolean switchAfterDownload,
             boolean forceDeactivateSim, @Nullable Bundle resolvedBundle) {
@@ -534,6 +537,37 @@
     /**
      * Download the given subscription.
      *
+     * @param slotIndex Index of the SIM slot to use for the operation.
+     * @param portIndex Index of the port from the slot. portIndex is used when
+     *     switchAfterDownload is set to {@code true}, otherwise download is port agnostic.
+     * @param subscription The subscription to download.
+     * @param switchAfterDownload If true, the subscription should be enabled upon successful
+     *     download.
+     * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
+     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM}
+     *     should be returned to allow the user to consent to this operation first.
+     * @param resolvedBundle The bundle containing information on resolved errors. It can contain
+     *     a string of confirmation code for the key {@link #EXTRA_RESOLUTION_CONFIRMATION_CODE},
+     *     and a boolean for key {@link #EXTRA_RESOLUTION_ALLOW_POLICY_RULES} indicating whether
+     *     the user allows profile policy rules or not.
+     * @return a DownloadSubscriptionResult instance including a result code, a resolvable errors
+     *     bit map, and original the card Id. The result code may be one of the predefined
+     *     {@code RESULT_} constants or any implementation-specific code starting with
+     *     {@link #RESULT_FIRST_USER}. The resolvable error bit map can be either 0 or values
+     *     defined in {@code RESOLVABLE_ERROR_}.
+     * @see android.telephony.euicc.EuiccManager#downloadSubscription
+     */
+    @NonNull
+    public DownloadSubscriptionResult onDownloadSubscription(int slotIndex, int portIndex,
+            @NonNull DownloadableSubscription subscription, boolean switchAfterDownload,
+            boolean forceDeactivateSim, @NonNull Bundle resolvedBundle) {
+        // stub implementation, LPA needs to implement this
+        throw new UnsupportedOperationException("LPA must override onDownloadSubscription");
+    }
+
+    /**
+     * Download the given subscription.
+     *
      * @param slotId ID of the SIM slot to use for the operation.
      * @param subscription The subscription to download.
      * @param switchAfterDownload If true, the subscription should be enabled upon successful
@@ -701,7 +735,8 @@
      */
     private class IEuiccServiceWrapper extends IEuiccService.Stub {
         @Override
-        public void downloadSubscription(int slotId, DownloadableSubscription subscription,
+        public void downloadSubscription(int slotId, int portIndex,
+                DownloadableSubscription subscription,
                 boolean switchAfterDownload, boolean forceDeactivateSim, Bundle resolvedBundle,
                 IDownloadSubscriptionCallback callback) {
             mExecutor.execute(new Runnable() {
diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl
index 030e11a..6b0397d 100644
--- a/telephony/java/android/service/euicc/IEuiccService.aidl
+++ b/telephony/java/android/service/euicc/IEuiccService.aidl
@@ -35,9 +35,9 @@
 
 /** @hide */
 oneway interface IEuiccService {
-    void downloadSubscription(int slotId, in DownloadableSubscription subscription,
-            boolean switchAfterDownload, boolean forceDeactivateSim, in Bundle resolvedBundle,
-            in IDownloadSubscriptionCallback callback);
+    void downloadSubscription(int slotId, int portIndex, in DownloadableSubscription subscription,
+                boolean switchAfterDownload, boolean forceDeactivateSim, in Bundle resolvedBundle,
+                in IDownloadSubscriptionCallback callback);
     void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription,
             boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback);
     void getEid(int slotId, in IGetEidCallback callback);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a1e37db..03be19f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5219,7 +5219,7 @@
          *     <li>{@link #KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY}</li>
          *     <li>{@link #KEY_CAPABILITY_TYPE_UT_INT_ARRAY}</li>
          *     <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li>
-         *     <li>{@link #KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY}</li>
+         *     <li>{@link #KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY}</li>
          * </ul>
          * <p> The values are defined in
          * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech}
@@ -5228,39 +5228,68 @@
                 KEY_PREFIX + "mmtel_requires_provisioning_bundle";
 
         /**
-         * This MmTelFeature supports Voice calling (IR.92)
+         * List of different RAT technologies on which Provisioning for Voice calling (IR.92)
+         * is supported.
          * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE
+         * <p>Possible values are,
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
          */
         public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY =
-                KEY_PREFIX + "key_capability_type_voice_int_array";
+                KEY_PREFIX + "capability_type_voice_int_array";
 
         /**
-         * This MmTelFeature supports Video (IR.94)
+         * List of different RAT technologies on which Provisioning for Video Telephony (IR.94)
+         * is supported.
          * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO
+         * <p>Possible values are,
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
          */
         public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY =
-                KEY_PREFIX + "key_capability_type_video_int_array";
+                KEY_PREFIX + "capability_type_video_int_array";
 
         /**
-         * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92)
+         * List of different RAT technologies on which Provisioning for XCAP over Ut for
+         * supplementary services. (IR.92) is supported.
          * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT
+         * <p>Possible values are,
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
          */
         public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY =
-                KEY_PREFIX + "key_capability_type_ut_int_array";
+                KEY_PREFIX + "capability_type_ut_int_array";
 
         /**
-         * This MmTelFeature supports SMS (IR.92)
+         * List of different RAT technologies on which Provisioning for SMS (IR.92) is supported.
          * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS
+         * <p>Possible values are,
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
          */
         public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY =
-                KEY_PREFIX + "key_capability_type_sms_int_array";
+                KEY_PREFIX + "capability_type_sms_int_array";
 
         /**
-         * This MmTelFeature supports Call Composer (section 2.4 of RCC.20)
+         * List of different RAT technologies on which Provisioning for Call Composer
+         * (section 2.4 of RCC.20) is supported.
          * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER
+         * <p>Possible values are,
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
          */
-        public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY =
-                KEY_PREFIX + "key_capability_type_call_composer_int_array";
+        public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY =
+                KEY_PREFIX + "capability_type_call_composer_int_array";
 
         /**
          * A bundle which specifies the RCS capability and registration technology
@@ -5283,9 +5312,14 @@
          * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
          * If not set, this RcsFeature should not service capability requests.
          * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE
+         * <p>Possible values are,
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
          */
         public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY =
-                KEY_PREFIX + "key_capability_type_options_uce_int_array";
+                KEY_PREFIX + "capability_type_options_uce_int_array";
 
         /**
          * This carrier supports User Capability Exchange using a presence server as defined by the
@@ -5293,9 +5327,14 @@
          * server. If not set, this RcsFeature should not publish capabilities or service capability
          * requests using presence.
          * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE
+         * <p>Possible values are,
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
          */
         public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY =
-                KEY_PREFIX + "key_capability_type_presence_uce_int_array";
+                KEY_PREFIX + "capability_type_presence_uce_int_array";
 
         private Ims() {}
 
@@ -5337,16 +5376,13 @@
             /**
              * @see #KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE
              */
-            PersistableBundle mmtel_requires_provisioning_int_array = new PersistableBundle();
             defaults.putPersistableBundle(
-                    KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, mmtel_requires_provisioning_int_array);
-
+                    KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, new PersistableBundle());
             /**
              * @see #KEY_RCS_REQUIRES_PROVISIONING_BUNDLE
              */
-            PersistableBundle rcs_requires_provisioning_int_array = new PersistableBundle();
             defaults.putPersistableBundle(
-                    KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, rcs_requires_provisioning_int_array);
+                    KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, new PersistableBundle());
 
             defaults.putBoolean(KEY_GRUU_ENABLED_BOOL, true);
             defaults.putBoolean(KEY_SIP_OVER_IPSEC_ENABLED_BOOL, true);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 81bcf75..613b0a6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -7118,7 +7118,14 @@
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public boolean iccCloseLogicalChannel(int channel) {
-        return iccCloseLogicalChannel(getSubId(), channel);
+        try {
+            return iccCloseLogicalChannel(getSubId(), channel);
+        } catch (IllegalStateException ex) {
+            Rlog.e(TAG, "iccCloseLogicalChannel IllegalStateException", ex);
+        } catch (IllegalArgumentException ex) {
+            Rlog.e(TAG, "iccCloseLogicalChannel IllegalArgumentException", ex);
+        }
+        return false;
     }
 
     /**
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index f614988..0a2bb3d 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -118,6 +118,9 @@
     /** Resets the default SM-DP+ address. */
     public static final int RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS = 1 << 2;
 
+    /** Result code when the requested profile is not found */
+    public static final int RESULT_PROFILE_NOT_FOUND = 1;
+
     /** Result code of execution with no error. */
     public static final int RESULT_OK = 0;
 
@@ -130,9 +133,6 @@
     /** Result code indicating the caller is not the active LPA. */
     public static final int RESULT_CALLER_NOT_ALLOWED = -3;
 
-    /** Result code when the requested profile is not found */
-    public static final int RESULT_PROFILE_NOT_FOUND = -4;
-
     /**
      * Callback to receive the result of an eUICC card API.
      *
@@ -223,7 +223,9 @@
     }
 
     /**
-     * Requests the enabled profile for a given port on an eUicc.
+     * Requests the enabled profile for a given port on an eUicc. Callback with result code
+     * {@link RESULT_PROFILE_NOT_FOUND} and {@code NULL} EuiccProfile if there is no enabled
+     * profile on the target port.
      *
      * @param cardId    The Id of the eUICC.
      * @param portIndex The portIndex to use. The port may be active or inactive. As long as the
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 8be7324..527159a 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -72,11 +72,16 @@
 
 // Build the android.test.base_static library
 // ==========================================
-// This is only intended for inclusion in the android.test.runner-minus-junit,
-// robolectric_android-all-stub and repackaged.android.test.* libraries.
+// This is only intended for use by the android.test.runner-minus-junit
+// library.
+//
 // Must not be used elsewhere.
+//
 java_library_static {
     name: "android.test.base_static",
+    visibility: [
+        "//frameworks/base/test-runner",
+    ],
     installable: false,
 
     srcs: [":android-test-base-sources"],
@@ -91,28 +96,10 @@
     sdk_version: "current",
 }
 
-// Build the repackaged.android.test.base library
-// ==============================================
-// This contains repackaged versions of the classes from
-// android.test.base.
-java_library_static {
-    name: "repackaged.android.test.base",
-
-    sdk_version: "current",
-    static_libs: ["android.test.base_static"],
-
-    jarjar_rules: "jarjar-rules.txt",
-    // Pin java_version until jarjar is certified to support later versions. http://b/72703434
-    java_version: "1.8",
-}
-
 // Build the android.test.base-minus-junit library
 // ===============================================
 // This contains the android.test classes from android.test.base plus
-// the com.android.internal.util.Predicate[s] classes. This is only
-// intended for inclusion in android.test.legacy and in
-// android.test.base-hiddenapi-annotations to avoid a dependency cycle and must
-// not be used elsewhere.
+// the com.android.internal.util.Predicate[s] classes.
 java_library_static {
     name: "android.test.base-minus-junit",
 
diff --git a/test-base/jarjar-rules.txt b/test-base/jarjar-rules.txt
deleted file mode 100644
index fd8555c..0000000
--- a/test-base/jarjar-rules.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rule junit.** repackaged.junit.@1
-rule android.test.** repackaged.android.test.@1
-rule com.android.internal.util.** repackaged.com.android.internal.util.@1
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 2a19af9..13a5dac 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -79,32 +79,6 @@
     ],
 }
 
-// Build the repackaged.android.test.runner library
-// ================================================
-java_library_static {
-    name: "repackaged.android.test.runner",
-
-    srcs: [":android-test-runner-sources"],
-    exclude_srcs: [
-        "src/android/test/ActivityUnitTestCase.java",
-        "src/android/test/ApplicationTestCase.java",
-        "src/android/test/IsolatedContext.java",
-        "src/android/test/ProviderTestCase.java",
-        "src/android/test/ProviderTestCase2.java",
-        "src/android/test/RenamingDelegatingContext.java",
-        "src/android/test/ServiceTestCase.java",
-    ],
-
-    sdk_version: "current",
-    libs: [
-        "android.test.base_static",
-    ],
-
-    jarjar_rules: "jarjar-rules.txt",
-    // Pin java_version until jarjar is certified to support later versions. http://b/72703434
-    java_version: "1.8",
-}
-
 // Make the current.txt available for use by the cts/tests/signature tests.
 // ========================================================================
 filegroup {
diff --git a/test-runner/jarjar-rules.txt b/test-runner/jarjar-rules.txt
deleted file mode 120000
index f6f7913..0000000
--- a/test-runner/jarjar-rules.txt
+++ /dev/null
@@ -1 +0,0 @@
-../test-base/jarjar-rules.txt
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 294a220..a425ee0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -142,7 +142,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 5)
+                .getConfigNonRotationTests(repetitions = 3)
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 519bd56..a089261 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -157,7 +157,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 5)
+                .getConfigNonRotationTests(repetitions = 3)
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index ca735031..8d60466 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -18,7 +18,6 @@
 
 import android.app.Instrumentation
 import android.platform.test.annotations.Presubmit
-import androidx.test.filters.FlakyTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerBuilderProvider
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -194,22 +193,4 @@
     open fun launcherLayerReplacesApp() {
         testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT)
     }
-
-    @FlakyTest
-    @Test
-    fun runPresubmitAssertion() {
-        flickerRule.checkPresubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runPostsubmitAssertion() {
-        flickerRule.checkPostsubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runFlakyAssertion() {
-        flickerRule.checkFlakyAssertions()
-    }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index c87d8e1..dd5f33f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -170,7 +170,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 5,
+                .getConfigNonRotationTests(repetitions = 3,
                     // b/190352379 (IME doesn't show on app launch in 90 degrees)
                     supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 815ea77..5606965 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -178,7 +178,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 5,
+                .getConfigNonRotationTests(repetitions = 3,
                     // b/190352379 (IME doesn't show on app launch in 90 degrees)
                     supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 24b1598..e7a1c50 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -160,7 +160,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 5)
+                .getConfigNonRotationTests(repetitions = 3)
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index e5d82a1..b454f01 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -164,7 +164,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 5,
+                    repetitions = 3,
                     supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index 005c4f5..9643909 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -140,7 +140,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 5,
+                    repetitions = 3,
                     supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 87f8ef2..8fcb4b7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -152,7 +152,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 5,
+                    repetitions = 3,
                     supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 5e06f11..5f0176e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -238,7 +238,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 5,
+                    repetitions = 3,
                     supportedRotations = listOf(Surface.ROTATION_0)
                 )
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 195af58..b5e13be 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -152,7 +152,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(repetitions = 5)
+                    .getConfigNonRotationTests(repetitions = 3)
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 3a2eba1..797919b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -130,7 +130,7 @@
     override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart()
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest(bugId = 218624176)
     @Test
     override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart()
 
@@ -149,6 +149,22 @@
         super.appWindowReplacesLauncherAsTopWindow()
     }
 
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        assumeFalse(isShellTransitionsEnabled)
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 218470989)
+    @Test
+    fun visibleWindowsShownMoreThanOneConsecutiveEntry_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
@@ -160,7 +176,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 5)
+                .getConfigNonRotationTests(repetitions = 3)
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 6365e7b..f75c50e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -160,7 +160,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(
-                            repetitions = 5,
+                            repetitions = 3,
                             supportedNavigationModes =
                             listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
                             supportedRotations = listOf(Surface.ROTATION_0)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 882e128..04fdda4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -244,7 +244,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(repetitions = 5)
+                    .getConfigNonRotationTests(repetitions = 3)
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 0a64939..5301e02 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -318,7 +318,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(
-                            repetitions = 5,
+                            repetitions = 3,
                             supportedNavigationModes = listOf(
                                     WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                             ),
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 42941c2..ce6a383 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -357,7 +357,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(
-                            repetitions = 5,
+                            repetitions = 3,
                             supportedNavigationModes = listOf(
                                     WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                             ),
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 8f2803e..1a762bf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -333,7 +333,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(
-                            repetitions = 5,
+                            repetitions = 3,
                             supportedNavigationModes = listOf(
                                     WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                             ),
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 3f0de7f..f603f6e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -25,11 +25,13 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Rule
 import org.junit.Test
@@ -94,24 +96,6 @@
             }
         }
 
-    @FlakyTest
-    @Test
-    fun runPresubmitAssertion() {
-        flickerRule.checkPresubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runPostsubmitAssertion() {
-        flickerRule.checkPostsubmitAssertions()
-    }
-
-    @FlakyTest
-    @Test
-    fun runFlakyAssertion() {
-        flickerRule.checkFlakyAssertions()
-    }
-
     /**
      * Windows maybe recreated when rotated. Checks that the focus does not change or if it does,
      * focus returns to [testApp]
@@ -131,6 +115,7 @@
     @Presubmit
     @Test
     fun rotationLayerAppearsAndVanishes() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
             this.isVisible(testApp.component)
                 .then()
@@ -141,6 +126,13 @@
         }
     }
 
+    @FlakyTest(bugId = 218484127)
+    @Test
+    fun rotationLayerAppearsAndVanishes_shellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        rotationLayerAppearsAndVanishes()
+    }
+
     /**
      * Checks that the status bar window is visible and above the app windows in all WM
      * trace entries
@@ -191,7 +183,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigRotationTests(repetitions = 5)
+                .getConfigRotationTests(repetitions = 3)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/HandwritingIme/Android.bp b/tests/HandwritingIme/Android.bp
new file mode 100644
index 0000000..1f552bf
--- /dev/null
+++ b/tests/HandwritingIme/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "HandwritingIme",
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+    certificate: "platform",
+    platform_apis: true,
+    static_libs: [
+        "androidx.core_core",
+        "androidx.appcompat_appcompat",
+        "com.google.android.material_material",
+    ],
+}
diff --git a/tests/HandwritingIme/AndroidManifest.xml b/tests/HandwritingIme/AndroidManifest.xml
new file mode 100644
index 0000000..1445d95
--- /dev/null
+++ b/tests/HandwritingIme/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (018C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.handwritingime">
+
+    <application android:label="Handwriting IME">
+        <service android:name=".HandwritingIme"
+                 android:process=":HandwritingIme"
+                 android:label="Handwriting IME"
+                 android:permission="android.permission.BIND_INPUT_METHOD"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.view.InputMethod"/>
+            </intent-filter>
+            <meta-data android:name="android.view.im"
+                       android:resource="@xml/ime"/>
+        </service>
+
+    </application>
+</manifest>
diff --git a/packages/SystemUI/res/layout/qs_detail_switch.xml b/tests/HandwritingIme/res/xml/ime.xml
similarity index 62%
copy from packages/SystemUI/res/layout/qs_detail_switch.xml
copy to tests/HandwritingIme/res/xml/ime.xml
index abb2497..2e84a03 100644
--- a/packages/SystemUI/res/layout/qs_detail_switch.xml
+++ b/tests/HandwritingIme/res/xml/ime.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 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,10 +15,6 @@
   ~ limitations under the License.
   -->
 
-<Switch
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/toggle"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:clickable="false"
-    android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
\ No newline at end of file
+<!-- Configuration info for an input method -->
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+              android:supportsStylusHandwriting="true"/>
\ No newline at end of file
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
new file mode 100644
index 0000000..18f9623
--- /dev/null
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.test.handwritingime;
+
+import android.annotation.Nullable;
+import android.inputmethodservice.InputMethodService;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import java.util.Random;
+
+public class HandwritingIme extends InputMethodService {
+
+    public static final int HEIGHT_DP = 100;
+
+    private Window mInkWindow;
+    private InkView mInk;
+
+    static final String TAG = "HandwritingIme";
+
+    interface HandwritingFinisher {
+        void finish();
+    }
+
+    interface StylusListener {
+        void onStylusEvent(MotionEvent me);
+    }
+
+    final class StylusConsumer implements StylusListener {
+        @Override
+        public void onStylusEvent(MotionEvent me) {
+            HandwritingIme.this.onStylusEvent(me);
+        }
+    }
+
+    final class HandwritingFinisherImpl implements HandwritingFinisher {
+
+        HandwritingFinisherImpl() {}
+
+        @Override
+        public void finish() {
+            finishStylusHandwriting();
+            Log.d(TAG, "HandwritingIme called finishStylusHandwriting() ");
+        }
+    }
+
+    private void onStylusEvent(@Nullable MotionEvent event) {
+        // TODO Hookup recognizer here
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            sendKeyChar((char) (56 + new Random().nextInt(66)));
+        }
+    }
+
+    @Override
+    public View onCreateInputView() {
+        Log.d(TAG, "onCreateInputView");
+        final ViewGroup view = new FrameLayout(this);
+        final View inner = new View(this);
+        final float density = getResources().getDisplayMetrics().density;
+        final int height = (int) (HEIGHT_DP * density);
+        view.setPadding(0, 0, 0, 0);
+        view.addView(inner, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT, height));
+        inner.setBackgroundColor(0xff0110fe); // blue
+
+        return view;
+    }
+
+    public void onPrepareStylusHandwriting() {
+        Log.d(TAG, "onPrepareStylusHandwriting ");
+        if (mInk == null) {
+            mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer());
+        }
+    }
+
+    @Override
+    public boolean onStartStylusHandwriting() {
+        Log.d(TAG, "onStartStylusHandwriting ");
+        Toast.makeText(this, "START HW", Toast.LENGTH_SHORT).show();
+        mInkWindow = getStylusHandwritingWindow();
+        mInkWindow.setContentView(mInk, mInk.getLayoutParams());
+        return true;
+    }
+
+    @Override
+    public void onFinishStylusHandwriting() {
+        Log.d(TAG, "onFinishStylusHandwriting ");
+        Toast.makeText(this, "Finish HW", Toast.LENGTH_SHORT).show();
+        // Free-up
+        ((ViewGroup) mInk.getParent()).removeView(mInk);
+        mInk = null;
+    }
+}
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
new file mode 100644
index 0000000..4ffdc92
--- /dev/null
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.google.android.test.handwritingime;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+class InkView extends View {
+    private static final long FINISH_TIMEOUT = 2500;
+    private final HandwritingIme.HandwritingFinisher mHwCanceller;
+    private final HandwritingIme.StylusConsumer mConsumer;
+    private Paint mPaint;
+    private Path  mPath;
+    private float mX, mY;
+    private static final float STYLUS_MOVE_TOLERANCE = 1;
+    private Runnable mFinishRunnable;
+
+    InkView(Context context, HandwritingIme.HandwritingFinisher hwController,
+            HandwritingIme.StylusConsumer consumer) {
+        super(context);
+        mHwCanceller = hwController;
+        mConsumer = consumer;
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setDither(true);
+        mPaint.setColor(Color.GREEN);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setStrokeJoin(Paint.Join.ROUND);
+        mPaint.setStrokeCap(Paint.Cap.ROUND);
+        mPaint.setStrokeWidth(14);
+
+        mPath = new Path();
+
+        WindowManager wm = context.getSystemService(WindowManager.class);
+        WindowMetrics metrics =  wm.getCurrentWindowMetrics();
+        Insets insets = metrics.getWindowInsets()
+                .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
+        setLayoutParams(new ViewGroup.LayoutParams(
+                metrics.getBounds().width() - insets.left - insets.right,
+                metrics.getBounds().height() - insets.top - insets.bottom));
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        canvas.drawPath(mPath, mPaint);
+        canvas.drawARGB(20, 255, 50, 50);
+    }
+
+    private void stylusStart(float x, float y) {
+        mPath.moveTo(x, y);
+        mX = x;
+        mY = y;
+    }
+
+    private void stylusMove(float x, float y) {
+        float dx = Math.abs(x - mX);
+        float dy = Math.abs(y - mY);
+        if (mPath.isEmpty()) {
+            stylusStart(x, y);
+        }
+        if (dx >= STYLUS_MOVE_TOLERANCE || dy >= STYLUS_MOVE_TOLERANCE) {
+            mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
+            mX = x;
+            mY = y;
+        }
+    }
+
+    private void stylusFinish() {
+        mPath.lineTo(mX, mY);
+        // TODO: support offscreen? e.g. mCanvas.drawPath(mPath, mPaint);
+        mPath.reset();
+        mX = 0;
+        mY = 0;
+
+    }
+
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) {
+            mConsumer.onStylusEvent(event);
+            android.util.Log.w(HandwritingIme.TAG, "INK touch onStylusEvent " + event);
+            float x = event.getX();
+            float y = event.getY();
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    cancelTimer();
+                    stylusStart(x, y);
+                    invalidate();
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    stylusMove(x, y);
+                    invalidate();
+                    break;
+
+                case MotionEvent.ACTION_UP:
+                    scheduleTimer();
+                    break;
+
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void cancelTimer() {
+        if (mFinishRunnable != null) {
+            if (getHandler() != null) {
+                getHandler().removeCallbacks(mFinishRunnable);
+            }
+            mFinishRunnable = null;
+        }
+        if (getHandler() != null) {
+            getHandler().removeCallbacksAndMessages(null);
+        }
+    }
+
+    private void scheduleTimer() {
+        cancelTimer();
+        if (getHandler() != null) {
+            postDelayed(getFinishRunnable(), FINISH_TIMEOUT);
+        }
+    }
+
+    private Runnable getFinishRunnable() {
+        mFinishRunnable = () -> {
+            android.util.Log.e(HandwritingIme.TAG, "Hw view timer finishHandwriting ");
+            mHwCanceller.finish();
+            stylusFinish();
+            mPath.reset();
+            invalidate();
+        };
+
+        return mFinishRunnable;
+    }
+
+}
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index 824f91e..15a6afc 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -62,6 +62,7 @@
             "android.view.PendingInsetsControllerTest",
             "android.window.", // all tests under the package.
             "android.app.activity.ActivityThreadTest",
+            "android.app.activity.RegisterComponentCallbacksTest"
     };
 
     public FrameworksTestsFilter(Bundle testArgs) {
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 1ec83a3..b18bdff 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -69,7 +69,6 @@
         "test/**/*.proto",
     ],
     proto: {
-        plugin: "javastream",
+        type: "stream",
     },
-    static_libs: ["libprotobuf-java-lite"],
 }
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index d3eb8e0..9604475 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -1269,18 +1269,19 @@
      * support the NL80211_CMD_REG_CHANGED (otherwise it will find out on its own). The wificond
      * updates in internal state in response to this Country Code update.
      *
-     * @return true on success, false otherwise.
+     * @param newCountryCode new country code. An ISO-3166-alpha2 country code which is 2-Character
+     *                       alphanumeric.
      */
-    public boolean notifyCountryCodeChanged() {
-        try {
-            if (mWificond != null) {
-                mWificond.notifyCountryCodeChanged();
-                return true;
-            }
-        } catch (RemoteException e1) {
-            Log.e(TAG, "Failed to notify country code changed due to remote exception");
+    public void notifyCountryCodeChanged(@Nullable String newCountryCode) {
+        if (mWificond == null) {
+            new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer();
         }
-        return false;
+        try {
+            mWificond.notifyCountryCodeChanged();
+            Log.i(TAG, "Receive country code change to " + newCountryCode);
+        } catch (RemoteException re) {
+            re.rethrowFromSystemServer();
+        }
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 4032a7b..a750696 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -1143,17 +1143,17 @@
     @Test
     public void testNotifyCountryCodeChanged() throws Exception {
         doNothing().when(mWificond).notifyCountryCodeChanged();
-        assertTrue(mWificondControl.notifyCountryCodeChanged());
+        mWificondControl.notifyCountryCodeChanged(TEST_COUNTRY_CODE);
         verify(mWificond).notifyCountryCodeChanged();
     }
 
     /**
      * Tests notifyCountryCodeChanged with RemoteException
      */
-    @Test
+    @Test(expected = RuntimeException.class)
     public void testNotifyCountryCodeChangedRemoteException() throws Exception {
         doThrow(new RemoteException()).when(mWificond).notifyCountryCodeChanged();
-        assertFalse(mWificondControl.notifyCountryCodeChanged());
+        mWificondControl.notifyCountryCodeChanged(TEST_COUNTRY_CODE);
         verify(mWificond).notifyCountryCodeChanged();
     }