Merge "[DO NOT MERGE] Prevent RemoteViews crashing SystemUi" into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index e38e21f..0650ce3 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -1970,7 +1970,7 @@
                 } break;
                 case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: {
                     updatePreIdleFactor();
-                    maybeDoImmediateMaintenance();
+                    maybeDoImmediateMaintenance("idle factor");
                 } break;
                 case MSG_REPORT_STATIONARY_STATUS: {
                     final DeviceIdleInternal.StationaryListener newListener =
@@ -3517,11 +3517,11 @@
                     // doze alarm to after the upcoming AlarmClock alarm.
                     scheduleAlarmLocked(
                             mAlarmManager.getNextWakeFromIdleTime() - mInjector.getElapsedRealtime()
-                                    + mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
+                                    + mConstants.QUICK_DOZE_DELAY_TIMEOUT);
                 } else {
                     // Wait a small amount of time in case something (eg: background service from
                     // recently closed app) needs to finish running.
-                    scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
+                    scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT);
                 }
             } else if (mState == STATE_ACTIVE) {
                 moveToStateLocked(STATE_INACTIVE, "no activity");
@@ -3536,9 +3536,9 @@
                     // alarm to after the upcoming AlarmClock alarm.
                     scheduleAlarmLocked(
                             mAlarmManager.getNextWakeFromIdleTime() - mInjector.getElapsedRealtime()
-                                    + delay, false);
+                                    + delay);
                 } else {
-                    scheduleAlarmLocked(delay, false);
+                    scheduleAlarmLocked(delay);
                 }
             }
         }
@@ -3753,7 +3753,7 @@
                 if (shouldUseIdleTimeoutFactorLocked()) {
                     delay = (long) (mPreIdleFactor * delay);
                 }
-                scheduleAlarmLocked(delay, false);
+                scheduleAlarmLocked(delay);
                 moveToStateLocked(STATE_IDLE_PENDING, reason);
                 break;
             case STATE_IDLE_PENDING:
@@ -3779,7 +3779,7 @@
             case STATE_SENSING:
                 cancelSensingTimeoutAlarmLocked();
                 moveToStateLocked(STATE_LOCATING, reason);
-                scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
+                scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT);
                 LocationManager locationManager = mInjector.getLocationManager();
                 if (locationManager != null
                         && locationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
@@ -3819,7 +3819,7 @@
                 // Everything is in place to go into IDLE state.
             case STATE_IDLE_MAINTENANCE:
                 moveToStateLocked(STATE_IDLE, reason);
-                scheduleAlarmLocked(mNextIdleDelay, true);
+                scheduleAlarmLocked(mNextIdleDelay);
                 if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
                         " ms.");
                 mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
@@ -3842,7 +3842,7 @@
                 mActiveIdleOpCount = 1;
                 mActiveIdleWakeLock.acquire();
                 moveToStateLocked(STATE_IDLE_MAINTENANCE, reason);
-                scheduleAlarmLocked(mNextIdlePendingDelay, false);
+                scheduleAlarmLocked(mNextIdlePendingDelay);
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
                         "Next alarm in " + mNextIdlePendingDelay + " ms.");
                 mMaintenanceStartTime = SystemClock.elapsedRealtime();
@@ -4013,19 +4013,18 @@
                 if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) {
                     return;
                 }
-                scheduleAlarmLocked(newDelay, false);
+                scheduleAlarmLocked(newDelay);
             }
         }
     }
 
-    private void maybeDoImmediateMaintenance() {
+    private void maybeDoImmediateMaintenance(String reason) {
         synchronized (this) {
             if (mState == STATE_IDLE) {
                 long duration = SystemClock.elapsedRealtime() - mIdleStartTime;
-                /* Let's trgger a immediate maintenance,
-                 * if it has been idle for a long time */
+                // Trigger an immediate maintenance window if it has been IDLE for long enough.
                 if (duration > mConstants.IDLE_TIMEOUT) {
-                    scheduleAlarmLocked(0, false);
+                    stepIdleStateLocked(reason);
                 }
             }
         }
@@ -4045,7 +4044,7 @@
     void setIdleStartTimeForTest(long idleStartTime) {
         synchronized (this) {
             mIdleStartTime = idleStartTime;
-            maybeDoImmediateMaintenance();
+            maybeDoImmediateMaintenance("testing");
         }
     }
 
@@ -4224,8 +4223,9 @@
     }
 
     @GuardedBy("this")
-    void scheduleAlarmLocked(long delay, boolean idleUntil) {
-        if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")");
+    @VisibleForTesting
+    void scheduleAlarmLocked(long delay) {
+        if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + stateToString(mState) + ")");
 
         if (mUseMotionSensor && mMotionSensor == null
                 && mState != STATE_QUICK_DOZE_DELAY
@@ -4241,7 +4241,7 @@
             return;
         }
         mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
-        if (idleUntil) {
+        if (mState == STATE_IDLE) {
             mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
         } else if (mState == STATE_LOCATING) {
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 394be6e..26c0eef 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -30,6 +30,9 @@
 import static android.app.AlarmManager.INTERVAL_HOUR;
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+import static android.content.PermissionChecker.PID_UNKNOWN;
+import static android.content.PermissionChecker.checkPermissionForPreflight;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.os.PowerExemptionManager.REASON_ALARM_MANAGER_ALARM_CLOCK;
 import static android.os.PowerExemptionManager.REASON_DENIED;
@@ -87,11 +90,9 @@
 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.content.pm.UserPackage;
-import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.BatteryStatsInternal;
@@ -182,7 +183,6 @@
 import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.TimeZone;
@@ -269,7 +269,8 @@
 
     /**
      * A map from uid to the last op-mode we have seen for
-     * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}
+     * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change
+     * when the denylist changes.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
@@ -2097,20 +2098,31 @@
                                 if (oldMode == newMode) {
                                     return;
                                 }
-                                final boolean allowedByDefault =
-                                        isScheduleExactAlarmAllowedByDefault(packageName, uid);
+                                final boolean deniedByDefault = isScheduleExactAlarmDeniedByDefault(
+                                        packageName, UserHandle.getUserId(uid));
 
                                 final boolean hadPermission;
-                                if (oldMode != AppOpsManager.MODE_DEFAULT) {
-                                    hadPermission = (oldMode == AppOpsManager.MODE_ALLOWED);
-                                } else {
-                                    hadPermission = allowedByDefault;
-                                }
                                 final boolean hasPermission;
-                                if (newMode != AppOpsManager.MODE_DEFAULT) {
-                                    hasPermission = (newMode == AppOpsManager.MODE_ALLOWED);
+
+                                if (deniedByDefault) {
+                                    final boolean permissionState = getContext().checkPermission(
+                                            Manifest.permission.SCHEDULE_EXACT_ALARM, PID_UNKNOWN,
+                                            uid) == PackageManager.PERMISSION_GRANTED;
+                                    hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT)
+                                            ? permissionState
+                                            : (oldMode == AppOpsManager.MODE_ALLOWED);
+                                    hasPermission = (newMode == AppOpsManager.MODE_DEFAULT)
+                                            ? permissionState
+                                            : (newMode == AppOpsManager.MODE_ALLOWED);
                                 } else {
-                                    hasPermission = allowedByDefault;
+                                    final boolean allowedByDefault =
+                                            !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
+                                    hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT)
+                                            ? allowedByDefault
+                                            : (oldMode == AppOpsManager.MODE_ALLOWED);
+                                    hasPermission = (newMode == AppOpsManager.MODE_DEFAULT)
+                                            ? allowedByDefault
+                                            : (newMode == AppOpsManager.MODE_ALLOWED);
                                 }
 
                                 if (hadPermission && !hasPermission) {
@@ -2754,41 +2766,13 @@
 
     boolean hasUseExactAlarmInternal(String packageName, int uid) {
         return isUseExactAlarmEnabled(packageName, UserHandle.getUserId(uid))
-                && (PermissionChecker.checkPermissionForPreflight(getContext(),
-                Manifest.permission.USE_EXACT_ALARM, PermissionChecker.PID_UNKNOWN, uid,
-                packageName) == PermissionChecker.PERMISSION_GRANTED);
-    }
-
-    /**
-     * Returns whether SCHEDULE_EXACT_ALARM is allowed by default.
-     */
-    boolean isScheduleExactAlarmAllowedByDefault(String packageName, int uid) {
-        if (isScheduleExactAlarmDeniedByDefault(packageName, UserHandle.getUserId(uid))) {
-
-            // This is essentially like changing the protection level of the permission to
-            // (privileged|signature|role|appop), but have to implement this logic to maintain
-            // compatibility for older apps.
-            if (mPackageManagerInternal.isPlatformSigned(packageName)
-                    || mPackageManagerInternal.isUidPrivileged(uid)) {
-                return true;
-            }
-            final long token = Binder.clearCallingIdentity();
-            try {
-                final List<String> wellbeingHolders = (mRoleManager != null)
-                        ? mRoleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING)
-                        : Collections.emptyList();
-                return wellbeingHolders.contains(packageName);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-        return !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
+                && (checkPermissionForPreflight(getContext(), Manifest.permission.USE_EXACT_ALARM,
+                PID_UNKNOWN, uid, packageName) == PERMISSION_GRANTED);
     }
 
     boolean hasScheduleExactAlarmInternal(String packageName, int uid) {
         final long start = mStatLogger.getTime();
 
-        // 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.
         final boolean hasPermission;
@@ -2796,11 +2780,16 @@
             hasPermission = false;
         } else if (!isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
             hasPermission = false;
+        } else if (isScheduleExactAlarmDeniedByDefault(packageName, UserHandle.getUserId(uid))) {
+            hasPermission = (checkPermissionForPreflight(getContext(),
+                    Manifest.permission.SCHEDULE_EXACT_ALARM, PID_UNKNOWN, uid, packageName)
+                    == PERMISSION_GRANTED);
         } else {
+            // Compatibility permission check for older apps.
             final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
                     packageName);
             if (mode == AppOpsManager.MODE_DEFAULT) {
-                hasPermission = isScheduleExactAlarmAllowedByDefault(packageName, uid);
+                hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
             } else {
                 hasPermission = (mode == AppOpsManager.MODE_ALLOWED);
             }
@@ -4685,10 +4674,6 @@
             return service.new ClockReceiver();
         }
 
-        void registerContentObserver(ContentObserver contentObserver, Uri uri) {
-            mContext.getContentResolver().registerContentObserver(uri, false, contentObserver);
-        }
-
         void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) {
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ALARM_MANAGER,
                     AppSchedulingModuleThread.getExecutor(), listener);
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 3cc67e7..056b6b9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -429,6 +429,7 @@
         public void onPropertiesChanged(DeviceConfig.Properties properties) {
             boolean apiQuotaScheduleUpdated = false;
             boolean concurrencyUpdated = false;
+            boolean persistenceUpdated = false;
             boolean runtimeUpdated = false;
             for (int controller = 0; controller < mControllers.size(); controller++) {
                 final StateController sc = mControllers.get(controller);
@@ -478,19 +479,23 @@
                         case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS:
                         case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
                         case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
-                        case Constants.KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS:
-                        case Constants.KEY_RUNTIME_USER_INITIATED_LIMIT_MS:
-                        case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
-                        case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS:
-                        case Constants.KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS:
+                        case Constants.KEY_RUNTIME_MIN_UI_GUARANTEE_MS:
+                        case Constants.KEY_RUNTIME_UI_LIMIT_MS:
+                        case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
+                        case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS:
+                        case Constants.KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS:
                             if (!runtimeUpdated) {
                                 mConstants.updateRuntimeConstantsLocked();
                                 runtimeUpdated = true;
                             }
                             break;
+                        case Constants.KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS:
                         case Constants.KEY_PERSIST_IN_SPLIT_FILES:
-                            mConstants.updatePersistingConstantsLocked();
-                            mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES);
+                            if (!persistenceUpdated) {
+                                mConstants.updatePersistingConstantsLocked();
+                                mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES);
+                                persistenceUpdated = true;
+                            }
                             break;
                         default:
                             if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
@@ -572,20 +577,20 @@
                 "runtime_free_quota_max_limit_ms";
         private static final String KEY_RUNTIME_MIN_GUARANTEE_MS = "runtime_min_guarantee_ms";
         private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";
-        private static final String KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
-                "runtime_min_user_initiated_guarantee_ms";
-        private static final String KEY_RUNTIME_USER_INITIATED_LIMIT_MS =
-                "runtime_user_initiated_limit_ms";
-        private static final String
-                KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
-                "runtime_min_user_initiated_data_transfer_guarantee_buffer_factor";
-        private static final String KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
-                "runtime_min_user_initiated_data_transfer_guarantee_ms";
-        private static final String KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
-                "runtime_user_initiated_data_transfer_limit_ms";
+        private static final String KEY_RUNTIME_MIN_UI_GUARANTEE_MS = "runtime_min_ui_guarantee_ms";
+        private static final String KEY_RUNTIME_UI_LIMIT_MS = "runtime_ui_limit_ms";
+        private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+                "runtime_min_ui_data_transfer_guarantee_buffer_factor";
+        private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
+                "runtime_min_ui_data_transfer_guarantee_ms";
+        private static final String KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS =
+                "runtime_ui_data_transfer_limit_ms";
 
         private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
 
+        private static final String KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS =
+                "max_num_persisted_job_work_items";
+
         private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
         private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
         private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
@@ -610,17 +615,18 @@
         public static final long DEFAULT_RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
         @VisibleForTesting
         public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
-        public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+        public static final long DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS =
                 Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
-        public static final long DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS =
+        public static final long DEFAULT_RUNTIME_UI_LIMIT_MS =
                 Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS);
-        public static final float
-                DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.35f;
-        public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
-                Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS);
-        public static final long DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
-                Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
+        public static final float DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+                1.35f;
+        public static final long DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
+                Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS);
+        public static final long DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS =
+                Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_UI_LIMIT_MS);
         static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
+        static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000;
 
         /**
          * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
@@ -731,33 +737,31 @@
         /**
          * The minimum amount of time we try to guarantee normal user-initiated jobs will run for.
          */
-        public long RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
-                DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+        public long RUNTIME_MIN_UI_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS;
 
         /**
          * The maximum amount of time we will let a user-initiated job run for. This will only
          * apply if there are no other limits that apply to the specific user-initiated job.
          */
-        public long RUNTIME_USER_INITIATED_LIMIT_MS = DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS;
+        public long RUNTIME_UI_LIMIT_MS = DEFAULT_RUNTIME_UI_LIMIT_MS;
 
         /**
          * A factor to apply to estimated transfer durations for user-initiated data transfer jobs
          * so that we give some extra time for unexpected situations. This will be at least 1 and
          * so can just be multiplied with the original value to get the final value.
          */
-        public float RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
-                DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR;
+        public float RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+                DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR;
 
         /**
          * The minimum amount of time we try to guarantee user-initiated data transfer jobs
          * will run for.
          */
-        public long RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
-                DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+        public long RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
+                DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
 
         /** The maximum amount of time we will let a user-initiated data transfer job run for. */
-        public long RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
-                DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+        public long RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS;
 
         /**
          * Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
@@ -766,6 +770,11 @@
         public boolean PERSIST_IN_SPLIT_FILES = DEFAULT_PERSIST_IN_SPLIT_FILES;
 
         /**
+         * The maximum number of {@link JobWorkItem JobWorkItems} that can be persisted per job.
+         */
+        public int MAX_NUM_PERSISTED_JOB_WORK_ITEMS = DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS;
+
+        /**
          * If true, use TARE policy for job limiting. If false, use quotas.
          */
         public boolean USE_TARE_POLICY = EconomyManager.DEFAULT_ENABLE_POLICY_JOB_SCHEDULER
@@ -827,6 +836,10 @@
         private void updatePersistingConstantsLocked() {
             PERSIST_IN_SPLIT_FILES = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_PERSIST_IN_SPLIT_FILES, DEFAULT_PERSIST_IN_SPLIT_FILES);
+            MAX_NUM_PERSISTED_JOB_WORK_ITEMS = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS,
+                    DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS);
         }
 
         private void updatePrefetchConstantsLocked() {
@@ -862,11 +875,11 @@
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS,
-                    KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
-                    KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
-                    KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
-                    KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
-                    KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS);
+                    KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+                    KEY_RUNTIME_MIN_UI_GUARANTEE_MS,
+                    KEY_RUNTIME_UI_LIMIT_MS,
+                    KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+                    KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS);
 
             // Make sure min runtime for regular jobs is at least 10 minutes.
             RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
@@ -880,37 +893,35 @@
                     properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                             DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
             // Make sure min runtime is at least as long as regular jobs.
-            RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
+            RUNTIME_MIN_UI_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
                     properties.getLong(
-                            KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
-                            DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS));
+                            KEY_RUNTIME_MIN_UI_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS));
             // Max limit should be at least the min guarantee AND the free quota.
-            RUNTIME_USER_INITIATED_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
-                    Math.max(RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+            RUNTIME_UI_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    Math.max(RUNTIME_MIN_UI_GUARANTEE_MS,
                             properties.getLong(
-                                    KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
-                                    DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS)));
+                                    KEY_RUNTIME_UI_LIMIT_MS, DEFAULT_RUNTIME_UI_LIMIT_MS)));
             // The buffer factor should be at least 1 (so we don't decrease the time).
-            RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = Math.max(1,
+            RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = Math.max(1,
                     properties.getFloat(
-                            KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
-                            DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR
+                            KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+                            DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR
                     ));
             // Make sure min runtime is at least as long as other user-initiated jobs.
-            RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = Math.max(
-                    RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+            RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = Math.max(
+                    RUNTIME_MIN_UI_GUARANTEE_MS,
                     properties.getLong(
-                            KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
-                            DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS));
+                            KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+                            DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
             // User-initiated requires RUN_USER_INITIATED_JOBS permission, so the upper limit will
             // be higher than other jobs.
             // Max limit should be the min guarantee and the max of other user-initiated jobs.
-            RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = Math.max(
-                    RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
-                    Math.max(RUNTIME_USER_INITIATED_LIMIT_MS,
+            RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = Math.max(
+                    RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+                    Math.max(RUNTIME_UI_LIMIT_MS,
                             properties.getLong(
-                                    KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
-                                    DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS)));
+                                    KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
+                                    DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS)));
         }
 
         private boolean updateTareSettingsLocked(@EconomyManager.EnabledMode int enabledMode) {
@@ -958,18 +969,18 @@
             pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
             pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
                     .println();
-            pw.print(KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
-                    RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS).println();
-            pw.print(KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
-                    RUNTIME_USER_INITIATED_LIMIT_MS).println();
-            pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
-                    RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
-            pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
-                    RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS).println();
-            pw.print(KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
-                    RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS).println();
+            pw.print(KEY_RUNTIME_MIN_UI_GUARANTEE_MS, RUNTIME_MIN_UI_GUARANTEE_MS).println();
+            pw.print(KEY_RUNTIME_UI_LIMIT_MS, RUNTIME_UI_LIMIT_MS).println();
+            pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+                    RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
+            pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+                    RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS).println();
+            pw.print(KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
+                    RUNTIME_UI_DATA_TRANSFER_LIMIT_MS).println();
 
             pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
+            pw.print(KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS, MAX_NUM_PERSISTED_JOB_WORK_ITEMS)
+                    .println();
 
             pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY).println();
 
@@ -1353,6 +1364,25 @@
                 // Fast path: we are adding work to an existing job, and the JobInfo is not
                 // changing.  We can just directly enqueue this work in to the job.
                 if (toCancel.getJob().equals(job)) {
+                    // On T and below, JobWorkItem count was unlimited but they could not be
+                    // persisted. Now in U and above, we allow persisting them. In both cases,
+                    // there is a danger of apps adding too many JobWorkItems and causing the
+                    // system to OOM since we keep everything in memory. The persisting danger
+                    // is greater because it could technically lead to a boot loop if the system
+                    // keeps trying to load all the JobWorkItems that led to the initial OOM.
+                    // Therefore, for now (partly for app compatibility), we tackle the latter
+                    // and limit the number of JobWorkItems that can be persisted.
+                    // Moving forward, we should look into two things:
+                    //   1. Limiting the number of unpersisted JobWorkItems
+                    //   2. Offloading some state to disk so we don't keep everything in memory
+                    // TODO(273758274): improve JobScheduler's resilience and memory management
+                    if (toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+                            && toCancel.isPersisted()) {
+                        Slog.w(TAG, "Too many JWIs for uid " + uId);
+                        throw new IllegalStateException("Apps may not persist more than "
+                                + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+                                + " JobWorkItems per job");
+                    }
 
                     toCancel.enqueueWorkLocked(work);
                     mJobs.touchJob(toCancel);
@@ -1397,6 +1427,26 @@
             jobStatus.prepareLocked();
 
             if (toCancel != null) {
+                // On T and below, JobWorkItem count was unlimited but they could not be
+                // persisted. Now in U and above, we allow persisting them. In both cases,
+                // there is a danger of apps adding too many JobWorkItems and causing the
+                // system to OOM since we keep everything in memory. The persisting danger
+                // is greater because it could technically lead to a boot loop if the system
+                // keeps trying to load all the JobWorkItems that led to the initial OOM.
+                // Therefore, for now (partly for app compatibility), we tackle the latter
+                // and limit the number of JobWorkItems that can be persisted.
+                // Moving forward, we should look into two things:
+                //   1. Limiting the number of unpersisted JobWorkItems
+                //   2. Offloading some state to disk so we don't keep everything in memory
+                // TODO(273758274): improve JobScheduler's resilience and memory management
+                if (work != null && toCancel.isPersisted()
+                        && toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS) {
+                    Slog.w(TAG, "Too many JWIs for uid " + uId);
+                    throw new IllegalStateException("Apps may not persist more than "
+                            + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+                            + " JobWorkItems per job");
+                }
+
                 // Implicitly replaces the existing job record with the new instance
                 cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
                         JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app");
@@ -1438,7 +1488,9 @@
                     /* isDeviceIdle */ false,
                     /* hasConnectivityConstraintSatisfied */ false,
                     /* hasContentTriggerConstraintSatisfied */ false,
-                    0);
+                    0,
+                    jobStatus.getJob().isUserInitiated(),
+                    /* isRunningAsUserInitiatedJob */ false);
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -1857,7 +1909,9 @@
                     cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
                     cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
                     cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
-                    0);
+                    0,
+                    cancelled.getJob().isUserInitiated(),
+                    /* isRunningAsUserInitiatedJob */ false);
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
@@ -3256,19 +3310,18 @@
                     final long estimatedTransferTimeMs =
                             mConnectivityController.getEstimatedTransferTimeMs(job);
                     if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
-                        return mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+                        return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
                     }
                     // Try to give the job at least as much time as we think the transfer will take,
                     // but cap it at the maximum limit
                     final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
-                            * mConstants
-                            .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
-                    return Math.min(mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+                            * mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
+                    return Math.min(mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
                             Math.max(factoredTransferTimeMs,
-                                    mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+                                    mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS
                             ));
                 }
-                return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+                return mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
             } else if (job.shouldTreatAsExpeditedJob()) {
                 // Don't guarantee RESTRICTED jobs more than 5 minutes.
                 return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
@@ -3287,10 +3340,10 @@
                     && checkRunUserInitiatedJobsPermission(
                             job.getSourceUid(), job.getSourcePackageName());
             if (job.getJob().getRequiredNetwork() != null && allowLongerJob) { // UI+DT
-                return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+                return mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS;
             }
             if (allowLongerJob) { // UI with LRJ permission
-                return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
+                return mConstants.RUNTIME_UI_LIMIT_MS;
             }
             if (job.shouldTreatAsUserInitiatedJob()) {
                 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index e60ed4a..4c339ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -462,7 +462,9 @@
                     job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
                     job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
                     job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
-                    mExecutionStartTimeElapsed - job.enqueueTime);
+                    mExecutionStartTimeElapsed - job.enqueueTime,
+                    job.getJob().isUserInitiated(),
+                    job.shouldTreatAsUserInitiatedJob());
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
                 // Use the context's ID to distinguish traces since there'll only be one job
                 // running per context.
@@ -1361,7 +1363,9 @@
                 completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
                 completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
                 completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
-                0);
+                0,
+                completedJob.getJob().isUserInitiated(),
+                completedJob.startedAsUserInitiatedJob);
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
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 537a670..0cc7758 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
@@ -814,6 +814,13 @@
         return null;
     }
 
+    /** Returns the number of {@link JobWorkItem JobWorkItems} attached to this job. */
+    public int getWorkCount() {
+        final int pendingCount = pendingWork == null ? 0 : pendingWork.size();
+        final int executingCount = executingWork == null ? 0 : executingWork.size();
+        return pendingCount + executingCount;
+    }
+
     public boolean hasWorkLocked() {
         return (pendingWork != null && pendingWork.size() > 0) || hasExecutingWorkLocked();
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
index 1ff389d..dffed0f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.content.Context;
 import android.content.PermissionChecker;
@@ -41,7 +42,8 @@
     @Nullable
     public final String installerPackageName;
 
-    InstalledPackageInfo(@NonNull Context context, @NonNull PackageInfo packageInfo) {
+    InstalledPackageInfo(@NonNull Context context, @UserIdInt int userId,
+            @NonNull PackageInfo packageInfo) {
         final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
         uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
         packageName = packageInfo.packageName;
@@ -55,7 +57,8 @@
                 applicationInfo.uid, packageName);
         InstallSourceInfo installSourceInfo = null;
         try {
-            installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName);
+            installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName,
+                    userId);
         } catch (RemoteException e) {
             // Shouldn't happen.
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index caf72e8..ffb2c03 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -625,7 +625,8 @@
             mPackageToUidCache.add(userId, pkgName, uid);
         }
         synchronized (mLock) {
-            final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), packageInfo);
+            final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), userId,
+                    packageInfo);
             final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo);
             maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             mUidToPackageCache.add(uid, pkgName);
@@ -683,7 +684,7 @@
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
                 final InstalledPackageInfo ipo =
-                        new InstalledPackageInfo(getContext(), pkgs.get(i));
+                        new InstalledPackageInfo(getContext(), userId, pkgs.get(i));
                 final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
                 maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
@@ -963,7 +964,7 @@
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
                 final InstalledPackageInfo ipo =
-                        new InstalledPackageInfo(getContext(), pkgs.get(i));
+                        new InstalledPackageInfo(getContext(), userId, pkgs.get(i));
                 final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
                 maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
diff --git a/api/api.go b/api/api.go
index 25d9728..9876abb 100644
--- a/api/api.go
+++ b/api/api.go
@@ -418,7 +418,6 @@
 // combined_apis bp2build converter
 func (a *CombinedApis) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
 	basePrefix := "non-updatable"
-	scopeNames := []string{"public", "system", "module-lib", "system-server"}
 	scopeToSuffix := map[string]string{
 		"public":        "-current.txt",
 		"system":        "-system-current.txt",
@@ -426,8 +425,7 @@
 		"system-server": "-system-server-current.txt",
 	}
 
-	for _, scopeName := range scopeNames{
-		suffix := scopeToSuffix[scopeName]
+	for scopeName, suffix := range scopeToSuffix{
 		name := a.Name() + suffix
 
 		var scope bazel.StringAttribute
diff --git a/core/api/current.txt b/core/api/current.txt
index 02de1cd..79123c7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8049,6 +8049,7 @@
     field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
     field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE";
     field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
+    field public static final String ACTION_DEVICE_FINANCING_STATE_CHANGED = "android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED";
     field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED";
     field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE";
@@ -27478,7 +27479,7 @@
     method public boolean onTrackballEvent(android.view.MotionEvent);
     method public abstract boolean onTune(android.net.Uri);
     method public boolean onTune(android.net.Uri, android.os.Bundle);
-    method public void onTvMessage(@NonNull String, @NonNull android.os.Bundle);
+    method public void onTvMessage(int, @NonNull android.os.Bundle);
     method public void onUnblockContent(android.media.tv.TvContentRating);
     method public void setOverlayViewEnabled(boolean);
   }
@@ -53907,6 +53908,14 @@
     method public void removeViewImmediate(android.view.View);
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+    field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
+    field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
+    field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
+    field public static final String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
+    field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
+    field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+    field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
+    field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
   }
 
   public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fb7ce5b..d592769 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9984,6 +9984,7 @@
   public final class HotspotNetwork implements android.os.Parcelable {
     method public int describeContents();
     method public long getDeviceId();
+    method @NonNull public android.os.Bundle getExtras();
     method public int getHostNetworkType();
     method @Nullable public String getHotspotBssid();
     method @NonNull public java.util.Set<java.lang.Integer> getHotspotSecurityTypes();
@@ -10003,6 +10004,7 @@
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder addHotspotSecurityType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setDeviceId(long);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHostNetworkType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHotspotBssid(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHotspotSsid(@NonNull String);
@@ -10039,6 +10041,7 @@
 
   public final class KnownNetwork implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public android.os.Bundle getExtras();
     method @Nullable public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo getNetworkProviderInfo();
     method public int getNetworkSource();
     method @NonNull public java.util.Set<java.lang.Integer> getSecurityTypes();
@@ -10054,6 +10057,7 @@
     ctor public KnownNetwork.Builder();
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder addSecurityType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork build();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkProviderInfo(@Nullable android.net.wifi.sharedconnectivity.app.NetworkProviderInfo);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkSource(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSsid(@NonNull String);
@@ -10085,6 +10089,7 @@
     method @IntRange(from=0, to=3) public int getConnectionStrength();
     method @NonNull public String getDeviceName();
     method public int getDeviceType();
+    method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getModelName();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
@@ -10103,6 +10108,7 @@
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setModelName(@NonNull String);
   }
 
@@ -10134,16 +10140,18 @@
   public final class SharedConnectivitySettingsState implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.os.Bundle getExtras();
+    method @Nullable public android.app.PendingIntent getInstantTetherSettingsPendingIntent();
     method public boolean isInstantTetherEnabled();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState> CREATOR;
   }
 
   public static final class SharedConnectivitySettingsState.Builder {
-    ctor public SharedConnectivitySettingsState.Builder();
+    ctor public SharedConnectivitySettingsState.Builder(@NonNull android.content.Context);
     method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setInstantTetherEnabled(boolean);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setInstantTetherSettingsPendingIntent(@NonNull android.content.Intent);
   }
 
 }
@@ -12993,20 +13001,20 @@
 package android.service.voice {
 
   public class AlwaysOnHotwordDetector implements android.service.voice.HotwordDetector {
-    method @Nullable public android.content.Intent createEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @Nullable public android.content.Intent createReEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @Nullable public android.content.Intent createUnEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+    method @Nullable public android.content.Intent createEnrollIntent();
+    method @Nullable public android.content.Intent createReEnrollIntent();
+    method @Nullable public android.content.Intent createUnEnrollIntent();
+    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int);
     method public int getSupportedAudioCapabilities();
-    method public int getSupportedRecognitionModes() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int, @NonNull byte[]) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+    method public int getSupportedRecognitionModes();
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int, @NonNull byte[]);
+    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int);
+    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 @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
+    method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
     field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
     field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
     field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
@@ -13185,10 +13193,10 @@
 
   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() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+    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();
+    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
   }
 
   public static interface HotwordDetector.Callback {
@@ -13202,9 +13210,6 @@
     method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
   }
 
-  public static class HotwordDetector.IllegalDetectorStateException extends android.util.AndroidException {
-  }
-
   public final class HotwordRejectedResult implements android.os.Parcelable {
     method public int describeContents();
     method public int getConfidenceLevel();
@@ -13272,9 +13277,9 @@
 
   public class VisualQueryDetector {
     method public void destroy();
-    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition();
+    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition();
+    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
   }
 
   public static interface VisualQueryDetector.Callback {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 445b957..6bfaa35 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -516,6 +516,10 @@
 
 package android.app.admin {
 
+  public final class AccountTypePolicyKey extends android.app.admin.PolicyKey {
+    ctor public AccountTypePolicyKey(@NonNull String, @NonNull String);
+  }
+
   public final class DeviceAdminAuthority extends android.app.admin.Authority {
     field @NonNull public static final android.app.admin.DeviceAdminAuthority DEVICE_ADMIN_AUTHORITY;
   }
@@ -612,6 +616,10 @@
     field @NonNull public static final android.app.admin.FlagUnion FLAG_UNION;
   }
 
+  public final class IntentFilterPolicyKey extends android.app.admin.PolicyKey {
+    ctor public IntentFilterPolicyKey(@NonNull String, @NonNull android.content.IntentFilter);
+  }
+
   public final class MostRecent<V> extends android.app.admin.ResolutionMechanism<V> {
     ctor public MostRecent();
     method public int describeContents();
@@ -627,6 +635,14 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRestrictive<?>> CREATOR;
   }
 
+  public final class PackagePermissionPolicyKey extends android.app.admin.PolicyKey {
+    ctor public PackagePermissionPolicyKey(@NonNull String, @NonNull String, @NonNull String);
+  }
+
+  public final class PackagePolicyKey extends android.app.admin.PolicyKey {
+    ctor public PackagePolicyKey(@NonNull String, @NonNull String);
+  }
+
   public final class PolicyState<V> implements android.os.Parcelable {
     method @NonNull public android.app.admin.ResolutionMechanism<V> getResolutionMechanism();
   }
@@ -676,6 +692,10 @@
     method public int getOperation();
   }
 
+  public final class UserRestrictionPolicyKey extends android.app.admin.PolicyKey {
+    ctor public UserRestrictionPolicyKey(@NonNull String, @NonNull String);
+  }
+
 }
 
 package android.app.assist {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2751b54..682fec8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5840,26 +5840,6 @@
 
         r.activity.mChangingConfigurations = true;
 
-        // If we are preserving the main window across relaunches we would also like to preserve
-        // the children. However the client side view system does not support preserving
-        // the child views so we notify the window manager to expect these windows to
-        // be replaced and defer requests to destroy or hide them. This way we can achieve
-        // visual continuity. It's important that we do this here prior to pause and destroy
-        // as that is when we may hide or remove the child views.
-        //
-        // There is another scenario, if we have decided locally to relaunch the app from a
-        // call to recreate, then none of the windows will be prepared for replacement or
-        // preserved by the server, so we want to notify it that we are preparing to replace
-        // everything
-        try {
-            if (r.mPreserveWindow) {
-                WindowManagerGlobal.getWindowSession().prepareToReplaceWindows(
-                        r.token, true /* childrenOnly */);
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
         handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                 pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
     }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6301ad7..999075d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2561,7 +2561,7 @@
     public InstallSourceInfo getInstallSourceInfo(String packageName) throws NameNotFoundException {
         final InstallSourceInfo installSourceInfo;
         try {
-            installSourceInfo = mPM.getInstallSourceInfo(packageName);
+            installSourceInfo = mPM.getInstallSourceInfo(packageName, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e78fb17..0e89f57 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4634,9 +4634,9 @@
          * Set whether this is an "ongoing" notification.
          *
          * Ongoing notifications cannot be dismissed by the user on locked devices, or by
-         * notification listeners, and some notifications  (device management, media) cannot be
-         * dismissed on unlocked devices, so your application or service must take
-         * care of canceling them.
+         * notification listeners, and some notifications (call, device management, media) cannot
+         * be dismissed on unlocked devices, so your application or service must take care of
+         * canceling them.
          *
          * They are typically used to indicate a background task that the user is actively engaged
          * with (e.g., playing music) or is pending in some way and therefore occupying the device
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e72b141..f7d2afb 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -54,6 +54,9 @@
 per-file Broadcast* = file:/BROADCASTS_OWNERS
 per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
 
+# KeyguardManager
+per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS
+
 # LocaleManager
 per-file *Locale* = file:/services/core/java/com/android/server/locales/OWNERS
 
@@ -94,7 +97,5 @@
 per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
 
-# TODO(b/174932174): determine the ownership of KeyguardManager.java
-
 # Zygote
 per-file *Zygote* = file:/ZYGOTE_OWNERS
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
index 6417cd4..9e376a7 100644
--- a/core/java/android/app/admin/AccountTypePolicyKey.java
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -49,6 +50,7 @@
     /**
      * @hide
      */
+    @TestApi
     public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
         super(key);
         mAccountType = Objects.requireNonNull((accountType));
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0241417..6bbbfe1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -57,6 +57,7 @@
 
 import android.Manifest.permission;
 import android.accounts.Account;
+import android.annotation.BroadcastBehavior;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
@@ -3998,6 +3999,27 @@
     public static final String EXTRA_RESOURCE_IDS =
             "android.app.extra.RESOURCE_IDS";
 
+    /**
+     * Broadcast Action: Broadcast sent to indicate that the device financing state has changed.
+     *
+     * <p>This occurs when, for example, a financing kiosk app has been added or removed.
+     *
+     * <p>To query the current device financing state see {@link #isDeviceFinanced}.
+     *
+     * <p>This will be delivered to the following apps if they include a receiver for this action
+     * in their manifest:
+     * <ul>
+     *     <li>Device owner admins.
+     *     <li>Organization-owned profile owner admins
+     *     <li>The supervision app
+     *     <li>The device management role holder
+     * </ul>
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    @BroadcastBehavior(explicitOnly = true, includeBackground = true)
+    public static final String ACTION_DEVICE_FINANCING_STATE_CHANGED =
+            "android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED";
+
     /** Allow the user to choose whether to enable MTE on the device. */
     public static final int MTE_NOT_CONTROLLED_BY_POLICY = 0;
 
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index b0af4cd..30aad96 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -49,6 +50,7 @@
     /**
      * @hide
      */
+    @TestApi
     public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
         super(identifier);
         mFilter = Objects.requireNonNull(filter);
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 08c4224..7fd514c 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -53,6 +54,7 @@
     /**
      * @hide
      */
+    @TestApi
     public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
             @NonNull String permissionName) {
         super(identifier);
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index b2a8d5d..2ab00bc 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -50,6 +51,7 @@
     /**
      * @hide
      */
+    @TestApi
     public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
         super(key);
         mPackageName = Objects.requireNonNull((packageName));
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
index 880b58b..aeb2380 100644
--- a/core/java/android/app/admin/UserRestrictionPolicyKey.java
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -40,6 +41,7 @@
     /**
      * @hide
      */
+    @TestApi
     public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
         super(identifier);
         mRestriction = Objects.requireNonNull(restriction);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 132b9af..410994d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -247,7 +247,7 @@
     @UnsupportedAppUsage
     String getInstallerPackageName(in String packageName);
 
-    InstallSourceInfo getInstallSourceInfo(in String packageName);
+    InstallSourceInfo getInstallSourceInfo(in String packageName, int userId);
 
     void resetApplicationPreferences(int userId);
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 328b0ae..b9c671a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8666,7 +8666,7 @@
      * requesting its own install information and is not an instant app.
      *
      * @param packageName The name of the package to query
-     * @throws NameNotFoundException if the given package name is not installed
+     * @throws NameNotFoundException if the given package name is not available to the caller.
      */
     @NonNull
     public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName)
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 3341800..5e523c0 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -513,6 +513,19 @@
      * This method executes within a transaction.  If an exception is thrown, all changes
      * will automatically be rolled back.
      * </p>
+     * <p>
+     * <em>Important:</em> You should NOT modify an existing migration step from version X to X+1
+     * once a build has been released containing that migration step.  If a migration step has an
+     * error and it runs on a device, the step will NOT re-run itself in the future if a fix is made
+     * to the migration step.</p>
+     * <p>For example, suppose a migration step renames a database column from {@code foo} to
+     * {@code bar} when the name should have been {@code baz}.  If that migration step is released
+     * in a build and runs on a user's device, the column will be renamed to {@code bar}.  If the
+     * developer subsequently edits this same migration step to change the name to {@code baz} as
+     * intended, the user devices which have already run this step will still have the name
+     * {@code bar}.  Instead, a NEW migration step should be created to correct the error and rename
+     * {@code bar} to {@code baz}, ensuring the error is corrected on devices which have already run
+     * the migration step with the error.</p>
      *
      * @param db The database.
      * @param oldVersion The old database version.
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
index cf20459..79a551a 100644
--- a/core/java/android/hardware/CameraSessionStats.java
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -54,6 +54,7 @@
     private int mApiLevel;
     private boolean mIsNdk;
     private int mLatencyMs;
+    private long mLogId;
     private int mSessionType;
     private int mInternalReconfigure;
     private long mRequestCount;
@@ -70,6 +71,7 @@
         mApiLevel = -1;
         mIsNdk = false;
         mLatencyMs = -1;
+        mLogId = 0;
         mMaxPreviewFps = 0;
         mSessionType = -1;
         mInternalReconfigure = -1;
@@ -82,7 +84,7 @@
 
     public CameraSessionStats(String cameraId, int facing, int newCameraState,
             String clientName, int apiLevel, boolean isNdk, int creationDuration,
-            float maxPreviewFps, int sessionType, int internalReconfigure) {
+            float maxPreviewFps, int sessionType, int internalReconfigure, long logId) {
         mCameraId = cameraId;
         mFacing = facing;
         mNewCameraState = newCameraState;
@@ -90,6 +92,7 @@
         mApiLevel = apiLevel;
         mIsNdk = isNdk;
         mLatencyMs = creationDuration;
+        mLogId = logId;
         mMaxPreviewFps = maxPreviewFps;
         mSessionType = sessionType;
         mInternalReconfigure = internalReconfigure;
@@ -127,6 +130,7 @@
         dest.writeInt(mApiLevel);
         dest.writeBoolean(mIsNdk);
         dest.writeInt(mLatencyMs);
+        dest.writeLong(mLogId);
         dest.writeFloat(mMaxPreviewFps);
         dest.writeInt(mSessionType);
         dest.writeInt(mInternalReconfigure);
@@ -146,6 +150,7 @@
         mApiLevel = in.readInt();
         mIsNdk = in.readBoolean();
         mLatencyMs = in.readInt();
+        mLogId = in.readLong();
         mMaxPreviewFps = in.readFloat();
         mSessionType = in.readInt();
         mInternalReconfigure = in.readInt();
@@ -189,6 +194,10 @@
         return mLatencyMs;
     }
 
+    public long getLogId() {
+        return mLogId;
+    }
+
     public float getMaxPreviewFps() {
         return mMaxPreviewFps;
     }
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index 644a2bf..0f3e8d1 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
-import android.app.compat.CompatChanges;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
 import android.os.Binder;
@@ -100,7 +99,7 @@
     public boolean startRecognition(
             @NonNull ParcelFileDescriptor audioStream,
             @NonNull AudioFormat audioFormat,
-            @Nullable PersistableBundle options) throws IllegalDetectorStateException {
+            @Nullable PersistableBundle options) {
         if (DEBUG) {
             Slog.i(TAG, "#recognizeHotword");
         }
@@ -132,18 +131,13 @@
      * @param sharedMemory The unrestricted data blob to provide to the
      *        {@link VisualQueryDetectionService} and {@link HotwordDetectionService}. Use this to
      *         provide the hotword models data or other such data to the trusted process.
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of
-     *         Android Tiramisu or above and attempts to start a recognition when the detector is
-     *         not able based on the state. Because the caller receives updates via an asynchronous
-     *         callback and the state of the detector can change without caller's knowledge, a
-     *         checked exception is thrown.
      * @throws IllegalStateException if this {@link HotwordDetector} wasn't specified to use a
      *         {@link HotwordDetectionService} or {@link VisualQueryDetectionService} when it was
      *         created.
      */
     @Override
     public void updateState(@Nullable PersistableBundle options,
-            @Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
+            @Nullable SharedMemory sharedMemory) {
         if (DEBUG) {
             Slog.d(TAG, "updateState()");
         }
@@ -199,13 +193,9 @@
         }
     }
 
-    protected void throwIfDetectorIsNoLongerActive() throws IllegalDetectorStateException {
+    protected void throwIfDetectorIsNoLongerActive() {
         if (!mIsDetectorActive.get()) {
             Slog.e(TAG, "attempting to use a destroyed detector which is no longer active");
-            if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                throw new IllegalDetectorStateException(
-                        "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");
         }
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 2830fb7..ffa15f0 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -827,28 +827,19 @@
     /**
      * {@inheritDoc}
      *
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above and this AlwaysOnHotwordDetector wasn't specified to use a
-     *         {@link HotwordDetectionService} when it was created. In addition, the exception can
-     *         be thrown if this AlwaysOnHotwordDetector is in an invalid or error state.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
-     *         this AlwaysOnHotwordDetector wasn't specified to use a
-     *         {@link HotwordDetectionService} when it was created. In addition, the exception can
-     *         be thrown if this AlwaysOnHotwordDetector is in an invalid or error state.
+     * @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a
+     * {@link HotwordDetectionService} when it was created. In addition, if this
+     * AlwaysOnHotwordDetector is in an invalid or error state.
      */
     @Override
     public final void updateState(@Nullable PersistableBundle options,
-            @Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
+            @Nullable SharedMemory sharedMemory) {
         synchronized (mLock) {
             if (!mSupportSandboxedDetectionService) {
                 throw new IllegalStateException(
                         "updateState called, but it doesn't support hotword detection service");
             }
             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
-                if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                    throw new IllegalDetectorStateException(
-                            "updateState called on an invalid detector or error state");
-                }
                 throw new IllegalStateException(
                         "updateState called on an invalid detector or error state");
             }
@@ -869,17 +860,16 @@
     @TestApi
     public void overrideAvailability(int availability) {
         synchronized (mLock) {
+            mAvailability = availability;
+            mIsAvailabilityOverriddenByTestApi = true;
             // ENROLLED state requires there to be metadata about the sound model so a fake one
             // is created.
-            if (mKeyphraseMetadata == null && availability == STATE_KEYPHRASE_ENROLLED) {
+            if (mKeyphraseMetadata == null && mAvailability == STATE_KEYPHRASE_ENROLLED) {
                 Set<Locale> fakeSupportedLocales = new HashSet<>();
                 fakeSupportedLocales.add(mLocale);
                 mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
                         AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
             }
-
-            mAvailability = availability;
-            mIsAvailabilityOverriddenByTestApi = true;
             notifyStateChangedLocked();
         }
     }
@@ -935,23 +925,14 @@
      * @see #RECOGNITION_MODE_USER_IDENTIFICATION
      * @see #RECOGNITION_MODE_VOICE_TRIGGER
      *
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above. Because the caller receives availability updates via an asynchronous
-     *         callback, it may be due to the availability changing while this call is performed.
-     *         - Throws if the detector is in an invalid or error state.
-     *           This may happen if another detector has been instantiated or the
-     *           {@link VoiceInteractionService} hosting this detector has been shut down.
-     * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
-     *         33 Android if the recognition isn't supported. Callers should only call this method
-     *         after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
-     *         avoid this exception.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below Android API level
-     *         33 if the detector is in an invalid or error state. This may happen if another
-     *         detector has been instantiated or the {@link VoiceInteractionService} hosting this
-     *         detector has been shut down.
+     * @throws UnsupportedOperationException if the keyphrase itself isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid or error state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
-    public @RecognitionModes
-    int getSupportedRecognitionModes() throws IllegalDetectorStateException {
+    public @RecognitionModes int getSupportedRecognitionModes() {
         if (DBG) Slog.d(TAG, "getSupportedRecognitionModes()");
         synchronized (mLock) {
             return getSupportedRecognitionModesLocked();
@@ -959,22 +940,14 @@
     }
 
     @GuardedBy("mLock")
-    private int getSupportedRecognitionModesLocked() throws IllegalDetectorStateException {
+    private int getSupportedRecognitionModesLocked() {
         if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
-            if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                throw new IllegalDetectorStateException("getSupportedRecognitionModes called on an"
-                        + " invalid detector or error state");
-            }
             throw new IllegalStateException(
                     "getSupportedRecognitionModes called on an invalid detector or error state");
         }
 
         // This method only makes sense if we can actually support a recognition.
         if (mAvailability != STATE_KEYPHRASE_ENROLLED || mKeyphraseMetadata == null) {
-            if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                throw new IllegalDetectorStateException("Getting supported recognition modes for"
-                        + " the keyphrase is not supported");
-            }
             throw new UnsupportedOperationException(
                     "Getting supported recognition modes for the keyphrase is not supported");
         }
@@ -1029,30 +1002,15 @@
      *             startRecognition request. This data is intended to provide additional parameters
      *             when starting the opaque sound model.
      * @return Indicates whether the call succeeded or not.
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above and attempts to start a recognition when the detector is not able based on
-     *         the availability state. This can be thrown even if the state has been checked before
-     *         calling this method because the caller receives availability updates via an
-     *         asynchronous callback, it may be due to the availability changing while this call is
-     *         performed.
-     *         - Throws if the recognition isn't supported.
-     *           Callers should only call this method after a supported state callback on
-     *           {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
-     *         - Also throws if the detector is in an invalid or error state.
-     *           This may happen if another detector has been instantiated or the
-     *           {@link VoiceInteractionService} hosting this detector has been shut down.
-     * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
-     *         33 Android if the recognition isn't supported. Callers should only call this method
-     *         after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
-     *         avoid this exception.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below Android API level
-     *         33 if the detector is in an invalid or error state. This may happen if another
-     *         detector has been instantiated or the {@link VoiceInteractionService} hosting this
-     *         detector has been shut down.
+     * @throws UnsupportedOperationException if the recognition isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid or error state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
-    public boolean startRecognition(@RecognitionFlags int recognitionFlags, @NonNull byte[] data)
-            throws IllegalDetectorStateException {
+    public boolean startRecognition(@RecognitionFlags int recognitionFlags, @NonNull byte[] data) {
         synchronized (mLock) {
             return startRecognitionLocked(recognitionFlags, data)
                     == STATUS_OK;
@@ -1069,30 +1027,15 @@
      *
      * @param recognitionFlags The flags to control the recognition properties.
      * @return Indicates whether the call succeeded or not.
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above and attempts to start a recognition when the detector is not able based on
-     *         the availability state. This can be thrown even if the state has been checked before
-     *         calling this method because the caller receives availability updates via an
-     *         asynchronous callback, it may be due to the availability changing while this call is
-     *         performed.
-     *         - Throws if the recognition isn't supported.
-     *           Callers should only call this method after a supported state callback on
-     *           {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
-     *         - Also throws if the detector is in an invalid or error state.
-     *           This may happen if another detector has been instantiated or the
-     *           {@link VoiceInteractionService} hosting this detector has been shut down.
-     * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
-     *         33 if the recognition isn't supported. Callers should only call this method after a
-     *         supported state callback on {@link Callback#onAvailabilityChanged(int)} to avoid this
-     *         exception.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
-     *         detector is in an invalid or error state. This may happen if another detector has
-     *         been instantiated or the {@link VoiceInteractionService} hosting this detector has
-     *         been shut down.
+     * @throws UnsupportedOperationException if the recognition isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid or error state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
-    public boolean startRecognition(@RecognitionFlags int recognitionFlags)
-            throws IllegalDetectorStateException {
+    public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
         if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
         synchronized (mLock) {
             return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK;
@@ -1106,8 +1049,7 @@
      */
     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
     @Override
-    public boolean startRecognition()
-            throws IllegalDetectorStateException {
+    public boolean startRecognition() {
         return startRecognition(0);
     }
 
@@ -1117,44 +1059,28 @@
      * Settings.Secure.VOICE_INTERACTION_SERVICE.
      *
      * @return Indicates whether the call succeeded or not.
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of
-     *         API level 33 or above and attempts to stop a recognition when the detector is
-     *         not able based on the state. This can be thrown even if the state has been checked
-     *         before calling this method because the caller receives availability updates via an
-     *         asynchronous callback, it may be due to the availability changing while this call is
-     *         performed.
-     * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
-     *         33 if the recognition isn't supported. Callers should only call this method after a
-     *         supported state callback on {@link Callback#onAvailabilityChanged(int)} to avoid this
-     *         exception.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
-     *         detector is in an invalid or error state. This may happen if another detector has
-     *         been instantiated or the {@link VoiceInteractionService} hosting this detector has
-     *         been shut down.
+     * @throws UnsupportedOperationException if the recognition isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid or error state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     // TODO: Remove this RequiresPermission since it isn't actually enforced. Also fix the javadoc
     // about permissions enforcement (when it throws vs when it just returns false) for other
     // methods in this class.
     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
     @Override
-    public boolean stopRecognition() throws IllegalDetectorStateException {
+    public boolean stopRecognition() {
         if (DBG) Slog.d(TAG, "stopRecognition()");
         synchronized (mLock) {
             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
-                if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                    throw new IllegalDetectorStateException(
-                            "stopRecognition called on an invalid detector or error state");
-                }
                 throw new IllegalStateException(
                         "stopRecognition called on an invalid detector or error state");
             }
 
             // Check if we can start/stop a recognition.
             if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
-                if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                    throw new IllegalDetectorStateException(
-                            "Recognition for the given keyphrase is not supported");
-                }
                 throw new UnsupportedOperationException(
                         "Recognition for the given keyphrase is not supported");
             }
@@ -1179,28 +1105,18 @@
      *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
      *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
      *           if API is not supported by HAL
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         if the detector is in an invalid or error state. This may happen if another detector
-     *         has been instantiated or the {@link VoiceInteractionService} hosting this detector
-     *         has been shut down.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
-     *         detector is in an invalid or error state. This may happen if another detector has
-     *         been instantiated or the {@link VoiceInteractionService} hosting this detector has
-     *         been shut down.
+     * @throws IllegalStateException if the detector is in an invalid or error state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
-    public int setParameter(@ModelParams int modelParam, int value)
-            throws IllegalDetectorStateException {
+    public int setParameter(@ModelParams int modelParam, int value) {
         if (DBG) {
             Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
         }
 
         synchronized (mLock) {
             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
-                if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                    throw new IllegalDetectorStateException(
-                            "setParameter called on an invalid detector or error state");
-                }
                 throw new IllegalStateException(
                         "setParameter called on an invalid detector or error state");
             }
@@ -1221,27 +1137,18 @@
      *
      * @param modelParam   {@link ModelParams}
      * @return value of parameter
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         if the detector is in an invalid or error state. This may happen if another detector
-     *         has been instantiated or the {@link VoiceInteractionService} hosting this detector
-     *         has been shut down.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
-     *         the detector is in an invalid or error state. This may happen if another detector has
-     *         been instantiated or the {@link VoiceInteractionService} hosting this detector has
-     *         been shut down.
+     * @throws IllegalStateException if the detector is in an invalid or error state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
-    public int getParameter(@ModelParams int modelParam) throws IllegalDetectorStateException {
+    public int getParameter(@ModelParams int modelParam) {
         if (DBG) {
             Slog.d(TAG, "getParameter(" + modelParam + ")");
         }
 
         synchronized (mLock) {
             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
-                if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                    throw new IllegalDetectorStateException(
-                            "getParameter called on an invalid detector or error state");
-                }
                 throw new IllegalStateException(
                         "getParameter called on an invalid detector or error state");
             }
@@ -1259,29 +1166,19 @@
      *
      * @param modelParam {@link ModelParams}
      * @return supported range of parameter, null if not supported
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         if the detector is in an invalid or error state. This may happen if another detector
-     *         has been instantiated or the {@link VoiceInteractionService} hosting this detector
-     *         has been shut down.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
-     *         the detector is in an invalid or error state. This may happen if another detector has
-     *         been instantiated or the {@link VoiceInteractionService} hosting this detector has
-     *         been shut down.
+     * @throws IllegalStateException if the detector is in an invalid or error state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
     @Nullable
-    public ModelParamRange queryParameter(@ModelParams int modelParam)
-            throws IllegalDetectorStateException {
+    public ModelParamRange queryParameter(@ModelParams int modelParam) {
         if (DBG) {
             Slog.d(TAG, "queryParameter(" + modelParam + ")");
         }
 
         synchronized (mLock) {
             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
-                if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                    throw new IllegalDetectorStateException(
-                            "queryParameter called on an invalid detector or error state");
-                }
                 throw new IllegalStateException(
                         "queryParameter called on an invalid detector or error state");
             }
@@ -1298,25 +1195,15 @@
      * otherwise {@link #createReEnrollIntent()} should be preferred.
      *
      * @return An {@link Intent} to start enrollment for the given keyphrase.
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above.
-     *         - Thrown if managing they keyphrase isn't supported. Callers should only call this
-     *           method after a supported state callback on
-     *           {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
-     *         - Thrown if the detector is in an invalid state. This may happen if another detector
-     *           has been instantiated or the {@link VoiceInteractionService} hosting this detector
-     *           has been shut down.
-     * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
-     *         33 if managing they keyphrase isn't supported. Callers should only call this method
-     *         after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
-     *         avoid this exception.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
-     *         detector is in an invalid state. This may happen if another detector has been
-     *         instantiated or the {@link VoiceInteractionService} hosting this detector has been
-     *         shut down.
+     * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     @Nullable
-    public Intent createEnrollIntent() throws IllegalDetectorStateException {
+    public Intent createEnrollIntent() {
         if (DBG) Slog.d(TAG, "createEnrollIntent");
         synchronized (mLock) {
             return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_ENROLL);
@@ -1330,25 +1217,15 @@
      * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
      *
      * @return An {@link Intent} to start un-enrollment for the given keyphrase.
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above.
-     *         - Thrown if managing they keyphrase isn't supported. Callers should only call this
-     *           method after a supported state callback on
-     *           {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
-     *         - Thrown if the detector is in an invalid state. This may happen if another detector
-     *           has been instantiated or the {@link VoiceInteractionService} hosting this detector
-     *           has been shut down.
-     * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
-     *         33 if managing they keyphrase isn't supported. Callers should only call this method
-     *         after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
-     *         avoid this exception.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
-     *         detector is in an invalid state. This may happen if another detector has been
-     *         instantiated or the {@link VoiceInteractionService} hosting this detector has been
-     *         shut down.
+     * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     @Nullable
-    public Intent createUnEnrollIntent() throws IllegalDetectorStateException {
+    public Intent createUnEnrollIntent() {
         if (DBG) Slog.d(TAG, "createUnEnrollIntent");
         synchronized (mLock) {
             return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_UN_ENROLL);
@@ -1362,25 +1239,15 @@
      * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
      *
      * @return An {@link Intent} to start re-enrollment for the given keyphrase.
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above.
-     *         - Thrown if managing they keyphrase isn't supported. Callers should only call this
-     *           method after a supported state callback on
-     *           {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
-     *         - Thrown if the detector is in an invalid state. This may happen if another detector
-     *           has been instantiated or the {@link VoiceInteractionService} hosting this detector
-     *           has been shut down.
-     * @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
-     *         33 if managing they keyphrase isn't supported. Callers should only call this method
-     *         after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
-     *         avoid this exception.
-     * @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
-     *         detector is in an invalid state. This may happen if another detector has been
-     *         instantiated or the {@link VoiceInteractionService} hosting this detector has been
-     *         shut down.
+     * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
+     * @throws IllegalStateException if the detector is in an invalid or error state.
+     *         This may happen if another detector has been instantiated or the
+     *         {@link VoiceInteractionService} hosting this detector has been shut down.
      */
     @Nullable
-    public Intent createReEnrollIntent() throws IllegalDetectorStateException {
+    public Intent createReEnrollIntent() {
         if (DBG) Slog.d(TAG, "createReEnrollIntent");
         synchronized (mLock) {
             return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_RE_ENROLL);
@@ -1388,24 +1255,15 @@
     }
 
     @GuardedBy("mLock")
-    private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action)
-            throws IllegalDetectorStateException {
+    private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) {
         if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
-            if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                throw new IllegalDetectorStateException(
-                        "getManageIntent called on an invalid detector or error state");
-            }
             throw new IllegalStateException(
-                "getManageIntent called on an invalid detector or error state");
+                    "getManageIntent called on an invalid detector or error state");
         }
 
         // This method only makes sense if we can actually support a recognition.
         if (mAvailability != STATE_KEYPHRASE_ENROLLED
                 && mAvailability != STATE_KEYPHRASE_UNENROLLED) {
-            if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                throw new IllegalDetectorStateException(
-                        "Managing the given keyphrase is not supported");
-            }
             throw new UnsupportedOperationException(
                     "Managing the given keyphrase is not supported");
         }
@@ -1489,27 +1347,19 @@
 
     @GuardedBy("mLock")
     private int startRecognitionLocked(int recognitionFlags,
-            @Nullable byte[] data) throws IllegalDetectorStateException {
+            @Nullable byte[] data) {
         if (DBG) {
             Slog.d(TAG, "startRecognition("
                     + recognitionFlags
                     + ", " + Arrays.toString(data) + ")");
         }
         if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
-            if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                throw new IllegalDetectorStateException(
-                        "startRecognition called on an invalid detector or error state");
-            }
             throw new IllegalStateException(
                     "startRecognition called on an invalid detector or error state");
         }
 
         // Check if we can start/stop a recognition.
         if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
-            if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
-                throw new IllegalDetectorStateException(
-                        "Recognition for the given keyphrase is not supported");
-            }
             throw new UnsupportedOperationException(
                     "Recognition for the given keyphrase is not supported");
         }
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 93fcec1..0c8fd48 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -23,14 +23,10 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
 import android.media.AudioFormat;
-import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.SharedMemory;
-import android.util.AndroidException;
 
 import java.io.PrintWriter;
 
@@ -44,23 +40,6 @@
 public interface HotwordDetector {
 
     /**
-     * Prior to API level 33, API calls of {@link android.service.voice.HotwordDetector} could
-     * return both {@link java.lang.IllegalStateException} or
-     * {@link java.lang.UnsupportedOperationException} depending on the detector's underlying state.
-     * This lead to confusing behavior as the underlying state of the detector can be modified
-     * without the knowledge of the caller via system service layer updates.
-     *
-     * This change ID, when enabled, changes the API calls to only throw checked exception
-     * {@link android.service.voice.HotwordDetector.IllegalDetectorStateException} when checking
-     * against state information modified by both the caller and the system services.
-     *
-     * @hide
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
-    long HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION = 226355112L;
-
-    /**
      * Indicates that it is a non-trusted hotword detector.
      *
      * @hide
@@ -109,26 +88,16 @@
      * Calling this again while recognition is active does nothing.
      *
      * @return {@code true} if the request to start recognition succeeded
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above and attempts to start a recognition when the detector is not able based on
-     *         the state. This can be thrown even if the state has been checked before calling this
-     *         method because the caller receives updates via an asynchronous callback, and the
-     *         state of the detector can change concurrently to the caller calling this method.
      */
     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
-    boolean startRecognition() throws IllegalDetectorStateException;
+    boolean startRecognition();
 
     /**
      * Stops sandboxed detection recognition.
      *
      * @return {@code true} if the request to stop recognition succeeded
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above and attempts to stop a recognition when the detector is not able based on
-     *         the state. This can be thrown even if the state has been checked before calling this
-     *         method because the caller receives updates via an asynchronous callback, and the
-     *         state of the detector can change concurrently to the caller calling this method.
      */
-    boolean stopRecognition() throws IllegalDetectorStateException;
+    boolean stopRecognition();
 
     /**
      * Starts hotword recognition on audio coming from an external connected microphone.
@@ -142,16 +111,11 @@
      *         PersistableBundle does not allow any remotable objects or other contents that can be
      *         used to communicate with other processes.
      * @return {@code true} if the request to start recognition succeeded
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above and attempts to start a recognition when the detector is not able based on
-     *         the state. This can be thrown even if the state has been checked before calling this
-     *         method because the caller receives updates via an asynchronous callback, and the
-     *         state of the detector can change concurrently to the caller calling this method.
      */
     boolean startRecognition(
             @NonNull ParcelFileDescriptor audioStream,
             @NonNull AudioFormat audioFormat,
-            @Nullable PersistableBundle options) throws IllegalDetectorStateException;
+            @Nullable PersistableBundle options);
 
     /**
      * Set configuration and pass read-only data to sandboxed detection service.
@@ -161,17 +125,10 @@
      * communicate with other processes.
      * @param sharedMemory The unrestricted data blob to provide to sandboxed detection services.
      * Use this to provide model data or other such data to the trusted process.
-     * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
-     *         or above and the detector is not able to perform the operation based on the
-     *         underlying state. This can be thrown even if the state has been checked before
-     *         calling this method because the caller receives updates via an asynchronous callback,
-     *         and the state of the detector can change concurrently to the caller calling this
-     *         method.
      * @throws IllegalStateException if this HotwordDetector wasn't specified to use a
      *         sandboxed detection service when it was created.
      */
-    void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory)
-            throws IllegalDetectorStateException;
+    void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory);
 
     /**
      * Invalidates this detector so that any future calls to this result
@@ -298,14 +255,4 @@
          */
         void onHotwordDetectionServiceRestarted();
     }
-
-    /**
-     * {@link HotwordDetector} specific exception thrown when the underlying state of the detector
-     * is invalid for the given action.
-     */
-    class IllegalDetectorStateException extends AndroidException {
-        IllegalDetectorStateException(String message) {
-            super(message);
-        }
-    }
 }
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 767fe37..77900d7 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -86,7 +86,7 @@
 
     @RequiresPermission(RECORD_AUDIO)
     @Override
-    public boolean startRecognition() throws IllegalDetectorStateException {
+    public boolean startRecognition() {
         if (DEBUG) {
             Slog.i(TAG, "#startRecognition");
         }
@@ -109,7 +109,7 @@
     /** TODO: stopRecognition */
     @RequiresPermission(RECORD_AUDIO)
     @Override
-    public boolean stopRecognition() throws IllegalDetectorStateException {
+    public boolean stopRecognition() {
         if (DEBUG) {
             Slog.i(TAG, "#stopRecognition");
         }
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 7dc0687..d7bf074 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -84,8 +84,7 @@
      * @see HotwordDetector#updateState(PersistableBundle, SharedMemory)
      */
     public void updateState(@Nullable PersistableBundle options,
-            @Nullable SharedMemory sharedMemory) throws
-            HotwordDetector.IllegalDetectorStateException {
+            @Nullable SharedMemory sharedMemory) {
         mInitializationDelegate.updateState(options, sharedMemory);
     }
 
@@ -104,7 +103,7 @@
      * @see HotwordDetector#startRecognition()
      */
     @RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
-    public boolean startRecognition() throws HotwordDetector.IllegalDetectorStateException {
+    public boolean startRecognition() {
         if (DEBUG) {
             Slog.i(TAG, "#startRecognition");
         }
@@ -128,7 +127,7 @@
      * @see HotwordDetector#stopRecognition()
      */
     @RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
-    public boolean stopRecognition() throws HotwordDetector.IllegalDetectorStateException {
+    public boolean stopRecognition() {
         if (DEBUG) {
             Slog.i(TAG, "#stopRecognition");
         }
@@ -236,13 +235,13 @@
         }
 
         @Override
-        public boolean stopRecognition() throws IllegalDetectorStateException {
+        public boolean stopRecognition() {
             throwIfDetectorIsNoLongerActive();
             return true;
         }
 
         @Override
-        public boolean startRecognition() throws IllegalDetectorStateException {
+        public boolean startRecognition() {
             throwIfDetectorIsNoLongerActive();
             return true;
         }
@@ -251,7 +250,7 @@
         public final boolean startRecognition(
                 @NonNull ParcelFileDescriptor audioStream,
                 @NonNull AudioFormat audioFormat,
-                @Nullable PersistableBundle options) throws IllegalDetectorStateException {
+                @Nullable PersistableBundle options) {
             //No-op, not supported by VisualQueryDetector as it should be trusted.
             return false;
         }
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 8c4e90c..c92b1b8 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -195,7 +195,7 @@
 
     private boolean mDebugPrintNextFrameTimeDelta;
     private int mFPSDivisor = 1;
-    private DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
+    private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
             new DisplayEventReceiver.VsyncEventData();
     private final FrameData mFrameData = new FrameData();
 
@@ -857,7 +857,7 @@
                 mFrameScheduled = false;
                 mLastFrameTimeNanos = frameTimeNanos;
                 mLastFrameIntervalNanos = frameIntervalNanos;
-                mLastVsyncEventData = vsyncEventData;
+                mLastVsyncEventData.copyFrom(vsyncEventData);
             }
 
             AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
@@ -1247,7 +1247,7 @@
         private boolean mHavePendingVsync;
         private long mTimestampNanos;
         private int mFrame;
-        private VsyncEventData mLastVsyncEventData = new VsyncEventData();
+        private final VsyncEventData mLastVsyncEventData = new VsyncEventData();
 
         FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) {
             super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle);
@@ -1287,7 +1287,7 @@
 
                 mTimestampNanos = timestampNanos;
                 mFrame = frame;
-                mLastVsyncEventData = vsyncEventData;
+                mLastVsyncEventData.copyFrom(vsyncEventData);
                 Message msg = Message.obtain(mHandler, this);
                 msg.setAsynchronous(true);
                 mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index b4675e0..54db34e 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -146,7 +146,12 @@
         mMessageQueue = null;
     }
 
-    static final class VsyncEventData {
+    /**
+     * Class to capture all inputs required for syncing events data.
+     *
+     * @hide
+     */
+    public static final class VsyncEventData {
         // The amount of frame timeline choices.
         // Must be in sync with VsyncEventData::kFrameTimelinesLength in
         // frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime
@@ -164,6 +169,12 @@
                 this.deadline = deadline;
             }
 
+            void copyFrom(FrameTimeline other) {
+                vsyncId = other.vsyncId;
+                expectedPresentationTime = other.expectedPresentationTime;
+                deadline = other.deadline;
+            }
+
             // The frame timeline vsync id, used to correlate a frame
             // produced by HWUI with the timeline data stored in Surface Flinger.
             public long vsyncId = FrameInfo.INVALID_VSYNC_ID;
@@ -203,6 +214,14 @@
             this.frameInterval = frameInterval;
         }
 
+        void copyFrom(VsyncEventData other) {
+            preferredFrameTimelineIndex = other.preferredFrameTimelineIndex;
+            frameInterval = other.frameInterval;
+            for (int i = 0; i < frameTimelines.length; i++) {
+                frameTimelines[i].copyFrom(other.frameTimelines[i]);
+            }
+        }
+
         public FrameTimeline preferredFrameTimeline() {
             return frameTimelines[preferredFrameTimelineIndex];
         }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 5810642..83de2a0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -110,16 +110,6 @@
             int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
             int lastSyncSeqId);
 
-    /*
-     * Notify the window manager that an application is relaunching and
-     * windows should be prepared for replacement.
-     *
-     * @param appToken The application
-     * @param childrenOnly Whether to only prepare child windows for replacement
-     * (for example when main windows are being reused via preservation).
-     */
-    oneway void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly);
-
     /**
      * Called by a client to report that it ran out of graphics memory.
      */
@@ -304,7 +294,7 @@
     * an input channel where the client can receive input.
     */
     void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
-            in IBinder hostInputToken, int flags, int privateFlags, int type,
+            in IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type,
             in IBinder windowToken, in IBinder focusGrantToken, String inputHandleName,
             out InputChannel outInputChannel);
 
@@ -312,7 +302,8 @@
      * Update the flags on an input channel associated with a particular surface.
      */
     oneway void updateInputChannel(in IBinder channelToken, int displayId,
-            in SurfaceControl surface, int flags, int privateFlags, in Region region);
+            in SurfaceControl surface, int flags, int privateFlags, int inputFeatures,
+            in Region region);
 
     /**
      * Transfer window focus to an embedded window if the calling window has focus.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 35ed88f..cc846e3 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -863,10 +863,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
             "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
 
@@ -899,8 +897,6 @@
      *     android:value="false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
     // TODO(b/263984287): Make this public API.
     String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
@@ -937,10 +933,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
 
     /**
@@ -976,10 +970,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
 
@@ -1023,10 +1015,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
 
@@ -1073,17 +1063,28 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
             "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
 
     /**
      * Application level {@link android.content.pm.PackageManager.Property PackageManager
      * .Property} for an app to inform the system that the app should be excluded from the
-     * compatibility override for orientation set by the device manufacturer.
+     * compatibility override for orientation set by the device manufacturer. When the orientation
+     * override is applied it can:
+     * <ul>
+     *   <li>Replace the specific orientation requested by the app with another selected by the
+             device manufacturer, e.g. replace undefined requested by the app with portrait.
+     *   <li>Always use an orientation selected by the device manufacturer.
+     *   <li>Do one of the above but only when camera connection is open.
+     * </ul>
+     *
+     * <p>This property is different from {@link PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
+     * (which is used to avoid orientation loops caused by the incorrect use of {@link
+     * android.app.Activity#setRequestedOrientation}) because this property overrides the app to an
+     * orientation selected by the device manufacturer rather than ignoring one of orientation
+     * requests coming from the app while respecting the previous one.
      *
      * <p>With this property set to {@code true} or unset, device manufacturers can override
      * orientation for the app using their discretion to improve display compatibility.
@@ -1099,10 +1100,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
 
@@ -1144,10 +1143,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
 
@@ -2815,7 +2812,7 @@
          *
          * @hide
          */
-        public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
+        public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 1 << 1;
 
         /**
          * By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers
@@ -2826,7 +2823,7 @@
          *
          * @hide
          */
-        public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
+        public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 1 << 2;
 
         /**
          * When set {@link LayoutParams#TYPE_APPLICATION_OVERLAY} windows will stay visible, even if
@@ -2835,7 +2832,7 @@
          * @hide
          */
         @RequiresPermission(permission.SYSTEM_APPLICATION_OVERLAY)
-        public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 0x00000008;
+        public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 1 << 3;
 
         /** In a multiuser system if this flag is set and the owner is a system process then this
          * window will appear on all user screens. This overrides the default behavior of window
@@ -2845,7 +2842,7 @@
          * {@hide} */
         @SystemApi
         @RequiresPermission(permission.INTERNAL_SYSTEM_WINDOW)
-        public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
+        public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 1 << 4;
 
         /**
          * Flag to allow this window to have unrestricted gesture exclusion.
@@ -2853,7 +2850,7 @@
          * @see View#setSystemGestureExclusionRects(List)
          * @hide
          */
-        public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 0x00000020;
+        public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 1 << 5;
 
         /**
          * Never animate position changes of the window.
@@ -2862,20 +2859,20 @@
          * {@hide}
          */
         @UnsupportedAppUsage
-        public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
+        public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 1 << 6;
 
         /** Window flag: special flag to limit the size of the window to be
          * original size ([320x480] x density). Used to create window for applications
          * running under compatibility mode.
          *
          * {@hide} */
-        public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;
+        public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 1 << 7;
 
         /** Window flag: a special option intended for system dialogs.  When
          * this flag is set, the window will demand focus unconditionally when
          * it is created.
          * {@hide} */
-        public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
+        public static final int PRIVATE_FLAG_SYSTEM_ERROR = 1 << 8;
 
         /**
          * Flag to indicate that the view hierarchy of the window can only be measured when
@@ -2884,14 +2881,14 @@
          * views. This reduces the chances to perform measure.
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200;
+        public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 1 << 9;
 
         /**
          * Flag that prevents the wallpaper behind the current window from receiving touch events.
          *
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
+        public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
 
         /**
          * Flag to force the status bar window to be visible all the time. If the bar is hidden when
@@ -2900,7 +2897,7 @@
          *
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000;
+        public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 1 << 11;
 
         /**
          * Flag to indicate that the window frame should be the requested frame adding the display
@@ -2910,7 +2907,7 @@
          *
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 0x00002000;
+        public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 1 << 12;
 
         /**
          * Flag that will make window ignore app visibility and instead depend purely on the decor
@@ -2918,39 +2915,28 @@
          * drawing after it launches an app.
          * @hide
          */
-        public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
-
-        /**
-         * Flag to indicate that this window is not expected to be replaced across
-         * configuration change triggered activity relaunches. In general the WindowManager
-         * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces
-         * until the replacement is ready to show in order to prevent visual glitch. However
-         * some windows, such as PopupWindows expect to be cleared across configuration change,
-         * and thus should hint to the WindowManager that it should not wait for a replacement.
-         * @hide
-         */
-        public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
+        public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 1 << 13;
 
         /**
          * Flag to indicate that this child window should always be laid-out in the parent
          * frame regardless of the current windowing mode configuration.
          * @hide
          */
-        public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 0x00010000;
+        public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 1 << 14;
 
         /**
          * Flag to indicate that this window is always drawing the status bar background, no matter
          * what the other flags are.
          * @hide
          */
-        public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 0x00020000;
+        public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 1 << 15;
 
         /**
          * Flag to indicate that this window needs Sustained Performance Mode if
          * the device supports it.
          * @hide
          */
-        public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 0x00040000;
+        public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 1 << 16;
 
         /**
          * Flag to indicate that any window added by an application process that is of type
@@ -2961,7 +2947,7 @@
          */
         @SystemApi
         @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
-        public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
+        public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 1 << 19;
 
         /**
          * Indicates that this window is the rounded corners overlay present on some
@@ -2969,7 +2955,7 @@
          * screen magnification, and mirroring.
          * @hide
          */
-        public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
+        public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 1 << 20;
 
         /**
          * Flag to indicate that this window will be excluded while computing the magnifiable region
@@ -2983,7 +2969,7 @@
          * </p><p>
          * @hide
          */
-        public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000;
+        public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 1 << 21;
 
         /**
          * Flag to prevent the window from being magnified by the accessibility magnifier.
@@ -2991,7 +2977,7 @@
          * TODO(b/190623172): This is a temporary solution and need to find out another way instead.
          * @hide
          */
-        public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 0x00400000;
+        public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
 
         /**
          * Flag to indicate that the status bar window is in a state such that it forces showing
@@ -3000,54 +2986,54 @@
          * It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}.
          * @hide
          */
-        public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 0x00800000;
+        public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 1 << 23;
 
         /**
          * Flag to indicate that the window is color space agnostic, and the color can be
          * interpreted to any color space.
          * @hide
          */
-        public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 0x01000000;
+        public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24;
 
         /**
          * Flag to request creation of a BLAST (Buffer as LayerState) Layer.
          * If not specified the client will receive a BufferQueue layer.
          * @hide
          */
-        public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000;
+        public static final int PRIVATE_FLAG_USE_BLAST = 1 << 25;
 
         /**
          * Flag to indicate that the window is controlling the appearance of system bars. So we
          * don't need to adjust it by reading its system UI flags for compatibility.
          * @hide
          */
-        public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 0x04000000;
+        public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 1 << 26;
 
         /**
          * Flag to indicate that the window is controlling the behavior of system bars. So we don't
          * need to adjust it by reading its window flags or system UI flags for compatibility.
          * @hide
          */
-        public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 0x08000000;
+        public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 1 << 27;
 
         /**
          * Flag to indicate that the window is controlling how it fits window insets on its own.
          * So we don't need to adjust its attributes for fitting window insets.
          * @hide
          */
-        public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000;
+        public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 1 << 28;
 
         /**
          * Flag to indicate that the window is a trusted overlay.
          * @hide
          */
-        public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000;
+        public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 1 << 29;
 
         /**
          * Flag to indicate that the parent frame of a window should be inset by IME.
          * @hide
          */
-        public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 0x40000000;
+        public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 1 << 30;
 
         /**
          * Flag to indicate that we want to intercept and handle global drag and drop for all users.
@@ -3062,7 +3048,7 @@
          * @hide
          */
         @RequiresPermission(permission.MANAGE_ACTIVITY_TASKS)
-        public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 0x80000000;
+        public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 1 << 31;
 
         /**
          * An internal annotation for flags that can be specified to {@link #softInputMode}.
@@ -3093,7 +3079,6 @@
                 PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
                 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
                 PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
-                PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
                 PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
                 PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS,
                 PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
@@ -3169,10 +3154,6 @@
                         equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
                         name = "FORCE_DECOR_VIEW_VISIBILITY"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
-                        equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
-                        name = "WILL_NOT_REPLACE_ON_RELAUNCH"),
-                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
                         equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
                         name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"),
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index b157ea0..0560caf 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -139,7 +139,7 @@
                 try {
                     mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId,
                             state.mSurfaceControl, state.mParams.flags, state.mParams.privateFlags,
-                            state.mInputRegion);
+                            state.mParams.inputFeatures, state.mInputRegion);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to update surface input channel: ", e);
                 }
@@ -189,12 +189,13 @@
                     mRealWm.grantInputChannel(displayId,
                             new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
                             window, mHostInputToken,
-                            attrs.flags, attrs.privateFlags, attrs.type, attrs.token,
-                            mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
+                            attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type,
+                            attrs.token, mFocusGrantToken, attrs.getTitle().toString(),
+                            outInputChannel);
                 } else {
                     mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
-                            attrs.privateFlags, attrs.type, attrs.token, mFocusGrantToken,
-                            attrs.getTitle().toString(), outInputChannel);
+                            attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token,
+                            mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to grant input to surface: ", e);
@@ -381,16 +382,19 @@
             outMergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
         }
 
-        if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
-                && state.mInputChannelToken != null) {
+        final int inputChangeMask = WindowManager.LayoutParams.FLAGS_CHANGED
+                | WindowManager.LayoutParams.INPUT_FEATURES_CHANGED;
+        if ((attrChanges & inputChangeMask) != 0 && state.mInputChannelToken != null) {
             try {
-                if(mRealWm instanceof IWindowSession.Stub) {
+                if (mRealWm instanceof IWindowSession.Stub) {
                     mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId,
                             new SurfaceControl(sc, "WindowlessWindowManager.relayout"),
-                            attrs.flags, attrs.privateFlags, state.mInputRegion);
+                            attrs.flags, attrs.privateFlags, attrs.inputFeatures,
+                            state.mInputRegion);
                 } else {
                     mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc,
-                            attrs.flags, attrs.privateFlags, state.mInputRegion);
+                            attrs.flags, attrs.privateFlags, attrs.inputFeatures,
+                            state.mInputRegion);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to update surface input channel: ", e);
@@ -415,10 +419,6 @@
     }
 
     @Override
-    public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
-    }
-
-    @Override
     public boolean outOfMemory(android.view.IWindow window) {
         return false;
     }
@@ -564,14 +564,14 @@
 
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
-            IBinder hostInputToken, int flags, int privateFlags, int type,
+            IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type,
             IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
             InputChannel outInputChannel) {
     }
 
     @Override
     public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface,
-            int flags, int privateFlags, Region region) {
+            int flags, int privateFlags, int inputFeatures, Region region) {
     }
 
     @Override
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index c1ec168..d54addb 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -19,7 +19,6 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1644,8 +1643,7 @@
             p.width = mLastWidth = mWidth;
         }
 
-        p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
-                | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+        p.privateFlags = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 
         // Used for debugging.
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 076e4e1..1505ccc 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -185,8 +185,13 @@
     private static void preloadSharedLibraries() {
         Log.i(TAG, "Preloading shared libraries...");
         System.loadLibrary("android");
-        System.loadLibrary("compiler_rt");
         System.loadLibrary("jnigraphics");
+
+        // TODO(b/206676167): This library is only used for renderscript today. When renderscript is
+        // removed, this load can be removed as well.
+        if (!SystemProperties.getBoolean("config.disable_renderscript", false)) {
+            System.loadLibrary("compiler_rt");
+        }
     }
 
     native private static void nativePreloadAppProcessHALs();
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 739055e..0c3ff6c 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -169,21 +169,25 @@
                           gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval,
                           vsyncEventData.frameInterval);
 
-        jobjectArray frameTimelinesObj = reinterpret_cast<jobjectArray>(
-                env->GetObjectField(vsyncEventDataObj.get(),
-                                    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo
-                                            .frameTimelines));
+        ScopedLocalRef<jobjectArray>
+                frameTimelinesObj(env,
+                                  reinterpret_cast<jobjectArray>(
+                                          env->GetObjectField(vsyncEventDataObj.get(),
+                                                              gDisplayEventReceiverClassInfo
+                                                                      .vsyncEventDataClassInfo
+                                                                      .frameTimelines)));
         for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
             VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i];
-            jobject frameTimelineObj = env->GetObjectArrayElement(frameTimelinesObj, i);
-            env->SetLongField(frameTimelineObj,
+            ScopedLocalRef<jobject>
+                    frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i));
+            env->SetLongField(frameTimelineObj.get(),
                               gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId,
                               frameTimeline.vsyncId);
-            env->SetLongField(frameTimelineObj,
+            env->SetLongField(frameTimelineObj.get(),
                               gDisplayEventReceiverClassInfo.frameTimelineClassInfo
                                       .expectedPresentationTime,
                               frameTimeline.expectedPresentationTime);
-            env->SetLongField(frameTimelineObj,
+            env->SetLongField(frameTimelineObj.get(),
                               gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline,
                               frameTimeline.deadlineTimestamp);
         }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 479ea4e..06e91c3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -824,6 +824,9 @@
     <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" />
     <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" />
     <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" />
+    <protected-broadcast android:name="android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.admin.action.DEVICE_POLICY_SET_RESULT" />
+    <protected-broadcast android:name="android.app.admin.action.DEVICE_POLICY_CHANGED" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -5303,12 +5306,12 @@
          {@link android.Manifest.permission#USE_EXACT_ALARM} once it targets API
          {@link android.os.Build.VERSION_CODES#TIRAMISU}. All apps using exact alarms for secondary
          features (which should still be user facing) should continue using this permission.
-         <p>Protection level: appop
+         <p>Protection level: signature|privileged|appop
      -->
     <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
         android:label="@string/permlab_schedule_exact_alarm"
         android:description="@string/permdesc_schedule_exact_alarm"
-        android:protectionLevel="normal|appop"/>
+        android:protectionLevel="signature|privileged|appop"/>
 
     <!-- Allows apps to use exact alarms just like with {@link
          android.Manifest.permission#SCHEDULE_EXACT_ALARM} but without needing to request this
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index e752431..8eae064 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -19,23 +19,28 @@
     android:id="@+id/expand_button"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
+    android:minHeight="@dimen/notification_header_height"
     android:layout_gravity="top|end"
     android:contentDescription="@string/expand_button_content_description_collapsed"
-    android:padding="16dp"
+    android:paddingHorizontal="16dp"
     >
 
     <LinearLayout
         android:id="@+id/expand_button_pill"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/notification_expand_button_pill_height"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/notification_expand_button_pill_height"
         android:orientation="horizontal"
         android:background="@drawable/expand_button_pill_bg"
+        android:gravity="center_vertical"
+        android:layout_gravity="center_vertical"
         >
 
         <TextView
             android:id="@+id/expand_button_number"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/notification_expand_button_pill_height"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/notification_expand_button_pill_height"
             android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
             android:gravity="center_vertical"
             android:paddingStart="8dp"
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index fd787f6..16a8bb7 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -79,7 +79,8 @@
             <NotificationTopLineView
                 android:id="@+id/notification_top_line"
                 android:layout_width="wrap_content"
-                android:layout_height="@dimen/notification_headerless_line_height"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/notification_headerless_line_height"
                 android:clipChildren="false"
                 android:theme="@style/Theme.DeviceDefault.Notification"
                 >
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
index 1b3bd26..76bcc96 100644
--- a/core/res/res/layout/notification_template_material_call.xml
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -29,7 +29,8 @@
     <LinearLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="88dp"
+        android:layout_height="wrap_content"
+        android:minHeight="88dp"
         android:orientation="horizontal"
         >
 
@@ -41,6 +42,7 @@
             android:layout_marginStart="@dimen/conversation_content_start"
             android:orientation="vertical"
             android:minHeight="68dp"
+            android:paddingBottom="@dimen/notification_headerless_margin_twoline"
             >
 
             <include
@@ -49,7 +51,10 @@
                 android:layout_height="wrap_content"
                 />
 
-            <include layout="@layout/notification_template_text" />
+            <include layout="@layout/notification_template_text"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/notification_text_height"
+                />
 
         </LinearLayout>
 
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 95ddc2e..df32d30 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -19,7 +19,8 @@
     android:id="@+id/status_bar_latest_event_content"
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/notification_min_height"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/notification_min_height"
     android:tag="media"
     >
 
@@ -77,7 +78,8 @@
             <NotificationTopLineView
                 android:id="@+id/notification_top_line"
                 android:layout_width="wrap_content"
-                android:layout_height="@dimen/notification_headerless_line_height"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/notification_headerless_line_height"
                 android:clipChildren="false"
                 android:theme="@style/Theme.DeviceDefault.Notification"
                 >
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index bef1d0b..3e82bd1 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -102,7 +102,8 @@
                     <NotificationTopLineView
                         android:id="@+id/notification_top_line"
                         android:layout_width="wrap_content"
-                        android:layout_height="@dimen/notification_headerless_line_height"
+                        android:layout_height="wrap_content"
+                        android:minHeight="@dimen/notification_headerless_line_height"
                         android:layout_marginStart="@dimen/notification_content_margin_start"
                         android:clipChildren="false"
                         android:theme="@style/Theme.DeviceDefault.Notification"
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index b35481d..97e753e 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -227,6 +227,12 @@
     <string-array name="device_state_notification_thermal_contents">
         <item>@string/concurrent_display_notification_thermal_content</item>
     </string-array>
+    <string-array name="device_state_notification_power_save_titles">
+        <item>@string/concurrent_display_notification_power_save_title</item>
+    </string-array>
+    <string-array name="device_state_notification_power_save_contents">
+        <item>@string/concurrent_display_notification_power_save_content</item>
+    </string-array>
 
     <!-- Certificate digests for trusted apps that will be allowed to obtain the knownSigner of the
          demo device provisioning permissions. -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5bb86dc..80bf795 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -356,7 +356,7 @@
     <dimen name="notification_headerless_margin_twoline">20dp</dimen>
 
     <!-- The height of each of the 1 or 2 lines in the headerless notification template -->
-    <dimen name="notification_headerless_line_height">24dp</dimen>
+    <dimen name="notification_headerless_line_height">24sp</dimen>
 
     <!-- vertical margin for the headerless notification content -->
     <dimen name="notification_headerless_min_height">56dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d8e69d7..6afdae5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6267,6 +6267,12 @@
     <string name="concurrent_display_notification_thermal_title">Device is too warm</string>
     <!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
     <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
+    <!-- Title of concurrent display power saver notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_power_save_title">Dual Screen is unavailable</string>
+    <!-- Content of concurrent display power saver notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_power_save_content">Dual Screen is unavailable because Battery Saver is on. You can turn this off in Settings.</string>
+    <!-- Text of power saver notification settings button. [CHAR LIMIT=NONE] -->
+    <string name="device_state_notification_settings_button">Go to Settings</string>
     <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
     <string name="device_state_notification_turn_off_button">Turn off</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c10612e..1cb56e0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4931,12 +4931,17 @@
   <java-symbol type="array" name="device_state_notification_active_contents"/>
   <java-symbol type="array" name="device_state_notification_thermal_titles"/>
   <java-symbol type="array" name="device_state_notification_thermal_contents"/>
+  <java-symbol type="array" name="device_state_notification_power_save_titles"/>
+  <java-symbol type="array" name="device_state_notification_power_save_contents"/>
   <java-symbol type="string" name="concurrent_display_notification_name"/>
   <java-symbol type="string" name="concurrent_display_notification_active_title"/>
   <java-symbol type="string" name="concurrent_display_notification_active_content"/>
   <java-symbol type="string" name="concurrent_display_notification_thermal_title"/>
   <java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
+  <java-symbol type="string" name="concurrent_display_notification_power_save_title"/>
+  <java-symbol type="string" name="concurrent_display_notification_power_save_content"/>
   <java-symbol type="string" name="device_state_notification_turn_off_button"/>
+  <java-symbol type="string" name="device_state_notification_settings_button"/>
   <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
   <java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" />
   <java-symbol type="string" name="config_rearDisplayPhysicalAddress" />
diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java
new file mode 100644
index 0000000..d7b911d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.DeleteRangeGesture.Builder#setGranularity",
+    "android.view.inputmethod.DeleteRangeGesture.Builder#setDeletionStartArea",
+    "android.view.inputmethod.DeleteRangeGesture.Builder#setDeletionEndArea",
+    "android.view.inputmethod.DeleteRangeGesture.Builder#setFallbackText",
+    "android.view.inputmethod.DeleteRangeGesture.Builder#build"})
+public class DeleteRangeGestureTest {
+    private static final RectF DELETION_START_RECTANGLE = new RectF(1, 2, 3, 4);
+    private static final RectF DELETION_END_RECTANGLE = new RectF(0, 2, 3, 4);
+    private static final String FALLBACK_TEXT = "fallback_test";
+
+    @Test
+    public void testBuilder() {
+        DeleteRangeGesture.Builder builder = new DeleteRangeGesture.Builder();
+        DeleteRangeGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setDeletionStartArea(DELETION_START_RECTANGLE)
+                .setDeletionEndArea(DELETION_END_RECTANGLE)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity());
+        assertEquals(DELETION_START_RECTANGLE, gesture.getDeletionStartArea());
+        assertEquals(DELETION_END_RECTANGLE, gesture.getDeletionEndArea());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java
new file mode 100644
index 0000000..47a724d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.PointF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.InsertGesture.Builder#setInsertionPoint",
+    "android.view.inputmethod.InsertGesture.Builder#setTextToInsert",
+    "android.view.inputmethod.InsertGesture.Builder#setFallbackText",
+    "android.view.inputmethod.InsertGesture.Builder#build"})
+public class InsertGestureTest {
+    private static final PointF INSERTION_POINT = new PointF(1, 2);
+    private static final String FALLBACK_TEXT = "fallback_text";
+    private static final String TEXT_TO_INSERT = "text";
+
+    @Test
+    public void testBuilder() {
+        InsertGesture.Builder builder = new InsertGesture.Builder();
+        InsertGesture gesture = builder.setInsertionPoint(INSERTION_POINT)
+                .setTextToInsert(TEXT_TO_INSERT)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(INSERTION_POINT, gesture.getInsertionPoint());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+        assertEquals(TEXT_TO_INSERT, gesture.getTextToInsert());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java
new file mode 100644
index 0000000..11ddba1
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.PointF;
+import android.os.CancellationSignal;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.InsertModeGesture.Builder#setInsertionPoint",
+    "android.view.inputmethod.InsertModeGesture.Builder#setCancellationSignal",
+    "android.view.inputmethod.InsertModeGesture.Builder#setFallbackText",
+    "android.view.inputmethod.InsertModeGesture.Builder#build"})
+public class InsertModeGestureTest {
+    private static final PointF INSERTION_POINT = new PointF(1, 2);
+    private static final String FALLBACK_TEXT = "fallback_text";
+    private static final CancellationSignal CANCELLATION_SIGNAL = new CancellationSignal();
+
+    @Test
+    public void testBuilder() {
+        InsertModeGesture.Builder builder = new InsertModeGesture.Builder();
+        InsertModeGesture gesture = builder.setInsertionPoint(INSERTION_POINT)
+                .setCancellationSignal(CANCELLATION_SIGNAL)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(INSERTION_POINT, gesture.getInsertionPoint());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+        assertEquals(CANCELLATION_SIGNAL, gesture.getCancellationSignal());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java
new file mode 100644
index 0000000..b2eb07c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.SelectGesture.Builder#setGranularity",
+    "android.view.inputmethod.SelectGesture.Builder#setSelectionArea",
+    "android.view.inputmethod.SelectGesture.Builder#setFallbackText",
+    "android.view.inputmethod.SelectGesture.Builder#build"})
+public class SelectGestureTest {
+    private static final RectF SELECTION_RECTANGLE = new RectF(1, 2, 3, 4);
+    private static final String FALLBACK_TEXT = "fallback_text";
+
+    @Test
+    public void testBuilder() {
+        SelectGesture.Builder builder = new SelectGesture.Builder();
+        SelectGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setSelectionArea(SELECTION_RECTANGLE)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity());
+        assertEquals(SELECTION_RECTANGLE, gesture.getSelectionArea());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java
new file mode 100644
index 0000000..df63a4a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.SelectRangeGesture.Builder#setGranularity",
+    "android.view.inputmethod.SelectRangeGesture.Builder#setSelectionStartArea",
+    "android.view.inputmethod.SelectRangeGesture.Builder#setSelectionEndArea",
+    "android.view.inputmethod.SelectRangeGesture.Builder#setFallbackText",
+    "android.view.inputmethod.SelectRangeGesture.Builder#build"})
+public class SelectRangeGestureTest {
+    private static final RectF SELECTION_START_RECTANGLE = new RectF(1, 2, 3, 4);
+    private static final RectF SELECTION_END_RECTANGLE = new RectF(0, 2, 3, 4);
+    private static final String FALLBACK_TEXT = "fallback_text";
+
+    @Test
+    public void testBuilder() {
+        SelectRangeGesture.Builder builder = new SelectRangeGesture.Builder();
+        SelectRangeGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setSelectionStartArea(SELECTION_START_RECTANGLE)
+                .setSelectionEndArea(SELECTION_END_RECTANGLE)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity());
+        assertEquals(SELECTION_START_RECTANGLE, gesture.getSelectionStartArea());
+        assertEquals(SELECTION_END_RECTANGLE, gesture.getSelectionEndArea());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+    }
+}
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index 2d6ae2e..19c52a6 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -20,6 +20,7 @@
         <permission name="android.permission.CALL_PRIVILEGED"/>
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+        <permission name="android.permission.SCHEDULE_EXACT_ALARM"/>
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
         <!-- Required to update emergency gesture settings -->
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5d303cf..0faf62e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -321,6 +321,7 @@
         <permission name="android.permission.REGISTER_CONNECTION_MANAGER"/>
         <permission name="android.permission.REGISTER_SIM_SUBSCRIPTION"/>
         <permission name="android.permission.RETRIEVE_WINDOW_CONTENT"/>
+        <permission name="android.permission.SCHEDULE_EXACT_ALARM"/>
         <permission name="android.permission.SET_ALWAYS_FINISH"/>
         <permission name="android.permission.SET_ANIMATION_SCALE"/>
         <permission name="android.permission.SET_DEBUG_APP"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ffc5ff2..5549f88 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -259,12 +259,6 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1878839956": {
-      "message": "Marking app token %s with replacing windows.",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1872288685": {
       "message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s",
       "level": "VERBOSE",
@@ -463,12 +457,6 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/Task.java"
     },
-    "-1698815688": {
-      "message": "Resetting app token %s of replacing window marks.",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1679411993": {
       "message": "setVr2dDisplayId called for: %d",
       "level": "DEBUG",
@@ -481,12 +469,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1661704580": {
-      "message": "Attempted to set replacing window on non-existing app token %s",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-1647332198": {
       "message": "remove RecentTask %s when finishing user %d",
       "level": "INFO",
@@ -613,12 +595,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1515151503": {
-      "message": ">>> OPEN TRANSACTION removeReplacedWindows",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
-    },
     "-1501564055": {
       "message": "Organized TaskFragment is not ready= %s",
       "level": "VERBOSE",
@@ -691,12 +667,6 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
-    "-1471946192": {
-      "message": "Marking app token %s with replacing child windows.",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1471518109": {
       "message": "Set animatingExit: reason=onAppVisibilityChanged win=%s",
       "level": "DEBUG",
@@ -919,12 +889,6 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
-    "-1270731689": {
-      "message": "Attempted to set replacing window on app token with no content %s",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-1263316010": {
       "message": "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and oldRotation=%s (%d)",
       "level": "VERBOSE",
@@ -1417,12 +1381,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
     },
-    "-799003045": {
-      "message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ANIM",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "-787664727": {
       "message": "Cannot launch dream activity due to invalid state. dream component: %s packageName: %s",
       "level": "ERROR",
@@ -1453,6 +1411,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-778347463": {
+      "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mDisplayFrozen=%b callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-775004869": {
       "message": "Not a match: %s",
       "level": "DEBUG",
@@ -1963,12 +1927,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-320419645": {
-      "message": "Removing replaced window: %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "-319689203": {
       "message": "Reparenting to original parent: %s for %s",
       "level": "INFO",
@@ -2335,12 +2293,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "38267433": {
-      "message": "Attempted to reset replacing window on non-existing app token %s",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "45285419": {
       "message": "startingWindow was set but startingSurface==null, couldn't remove",
       "level": "VERBOSE",
@@ -2989,12 +2941,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "594260654": {
-      "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_APP_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "600140673": {
       "message": "checkBootAnimationComplete: Waiting for anim complete",
       "level": "INFO",
@@ -3829,12 +3775,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "1423592961": {
-      "message": "<<< CLOSE TRANSACTION removeReplacedWindows",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
-    },
     "1430336882": {
       "message": "findFocusedWindow: focusedApp windows not focusable using new focus @ %s",
       "level": "VERBOSE",
@@ -3901,12 +3841,6 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
-    "1515161239": {
-      "message": "removeDeadWindows: %s",
-      "level": "WARN",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1518495446": {
       "message": "removeWindowToken: Attempted to remove non-existing token: %s",
       "level": "WARN",
@@ -4273,12 +4207,6 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
     },
-    "1921821199": {
-      "message": "Preserving %s until the new one is added",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "1928325128": {
       "message": "Run showImeRunner",
       "level": "DEBUG",
@@ -4489,12 +4417,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
-    "2114149926": {
-      "message": "Not removing %s because app died while it's visible",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "2117696413": {
       "message": "moveTaskToFront: moving taskId=%d",
       "level": "DEBUG",
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 298ad30..8d1da0f7 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -63,11 +63,11 @@
             android:tint="@color/bubbles_icon_tint"/>
 
         <TextView
+            android:id="@+id/bubble_manage_menu_dont_bubble_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="16dp"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
-            android:text="@string/bubbles_dont_bubble_conversation" />
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
 
     </LinearLayout>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 3082962..9f6cf79 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -146,6 +146,8 @@
     <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
     <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
     <string name="bubble_dismiss_text">Dismiss bubble</string>
+    <!-- Button text to stop an app from bubbling [CHAR LIMIT=60]-->
+    <string name="bubbles_dont_bubble">Don\u2019t bubble</string>
     <!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]-->
     <string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string>
     <!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 0b947c8..deb4fd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -844,6 +844,8 @@
     private DismissView mDismissView;
 
     private ViewGroup mManageMenu;
+    private TextView mManageDontBubbleText;
+    private ViewGroup mManageSettingsView;
     private ImageView mManageSettingsIcon;
     private TextView mManageSettingsText;
     private boolean mShowingManage = false;
@@ -1217,7 +1219,11 @@
                     mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey());
                 });
 
-        mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+        mManageDontBubbleText = mManageMenu
+                .findViewById(R.id.bubble_manage_menu_dont_bubble_text);
+
+        mManageSettingsView = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container);
+        mManageSettingsView.setOnClickListener(
                 view -> {
                     showManageMenu(false /* show */);
                     final BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
@@ -2868,10 +2874,19 @@
         // name and icon.
         if (show) {
             final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
-            if (bubble != null) {
+            if (bubble != null && !bubble.isAppBubble()) {
+                // Setup options for non app bubbles
+                mManageDontBubbleText.setText(R.string.bubbles_dont_bubble_conversation);
                 mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge());
                 mManageSettingsText.setText(getResources().getString(
                         R.string.bubbles_app_settings, bubble.getAppName()));
+                mManageSettingsView.setVisibility(VISIBLE);
+            } else {
+                // Setup options for app bubbles
+                mManageDontBubbleText.setText(R.string.bubbles_dont_bubble);
+                // App bubbles are not notification based
+                // so we don't show the option to go to notification settings
+                mManageSettingsView.setVisibility(GONE);
             }
         }
 
@@ -2936,6 +2951,15 @@
         }
     }
 
+    /**
+     * Checks whether manage menu notification settings action is available and visible
+     * Used for testing
+     */
+    @VisibleForTesting
+    public boolean isManageMenuSettingsVisible() {
+        return mManageSettingsView != null && mManageSettingsView.getVisibility() == VISIBLE;
+    }
+
     private void updateExpandedBubble() {
         if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updateExpandedBubble()");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index be9b529..ea2559a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -104,7 +104,6 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -119,6 +118,8 @@
         UserChangeListener {
     private static final String TAG = "PipController";
 
+    private static final String LAUNCHER_KEEP_CLEAR_AREA_TAG = "hotseat";
+
     private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
             SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
 
@@ -934,12 +935,10 @@
                     0, mPipBoundsState.getDisplayBounds().bottom - height,
                     mPipBoundsState.getDisplayBounds().right,
                     mPipBoundsState.getDisplayBounds().bottom);
-            Set<Rect> restrictedKeepClearAreas = new HashSet<>(
-                    mPipBoundsState.getRestrictedKeepClearAreas());
-            restrictedKeepClearAreas.add(rect);
-            mPipBoundsState.setKeepClearAreas(restrictedKeepClearAreas,
-                    mPipBoundsState.getUnrestrictedKeepClearAreas());
+            mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
             updatePipPositionForKeepClearAreas();
+        } else {
+            mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
         }
     }
 
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 a5546e5..a1eaf85 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
@@ -259,37 +259,6 @@
                 }
             };
 
-    private final SplitScreenTransitions.TransitionFinishedCallback
-            mRecentTransitionFinishedCallback =
-            new SplitScreenTransitions.TransitionFinishedCallback() {
-                @Override
-                public void onFinished(WindowContainerTransaction finishWct,
-                        SurfaceControl.Transaction finishT) {
-                    // Check if the recent transition is finished by returning to the current
-                    // split, so we
-                    // can restore the divider bar.
-                    for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
-                        final WindowContainerTransaction.HierarchyOp op =
-                                finishWct.getHierarchyOps().get(i);
-                        final IBinder container = op.getContainer();
-                        if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
-                                && (mMainStage.containsContainer(container)
-                                || mSideStage.containsContainer(container))) {
-                            updateSurfaceBounds(mSplitLayout, finishT,
-                                    false /* applyResizingOffset */);
-                            setDividerVisibility(true, finishT);
-                            return;
-                        }
-                    }
-
-                    // Dismiss the split screen if it's not returning to split.
-                    prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
-                    setSplitsVisible(false);
-                    setDividerVisibility(false, finishT);
-                    logExit(EXIT_REASON_UNKNOWN);
-                }
-            };
-
     protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             ShellTaskOrganizer taskOrganizer, DisplayController displayController,
             DisplayImeController displayImeController,
@@ -388,6 +357,11 @@
         return mMainStage.isActive();
     }
 
+    /** Checks if `transition` is a pending enter-split transition. */
+    public boolean isPendingEnter(IBinder transition) {
+        return mSplitTransitions.isPendingEnter(transition);
+    }
+
     @StageType
     int getStageOfTask(int taskId) {
         if (mMainStage.containsTask(taskId)) {
@@ -2264,11 +2238,16 @@
                 }
             } else if (isOpening && inFullscreen) {
                 final int activityType = triggerTask.getActivityType();
-                if (activityType == ACTIVITY_TYPE_HOME
-                        || activityType == ACTIVITY_TYPE_RECENTS) {
-                    // Enter overview panel, so start recent transition.
-                    mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
-                            mRecentTransitionFinishedCallback);
+                if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+                    if (request.getRemoteTransition() != null) {
+                        // starting recents/home, so don't handle this and let it fall-through to
+                        // the remote handler.
+                        return null;
+                    }
+                    // Need to use the old stuff for non-remote animations, otherwise we don't
+                    // exit split-screen.
+                    mSplitTransitions.setRecentTransition(transition, null /* remote */,
+                            this::onRecentsInSplitAnimationFinish);
                 }
             }
         } else {
@@ -2398,7 +2377,7 @@
             shouldAnimate = startPendingEnterAnimation(
                     transition, info, startTransaction, finishTransaction);
         } else if (mSplitTransitions.isPendingRecent(transition)) {
-            shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
+            onRecentsInSplitAnimationStart(startTransaction);
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             shouldAnimate = startPendingDismissAnimation(
                     mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
@@ -2653,10 +2632,35 @@
         return true;
     }
 
-    private boolean startPendingRecentAnimation(@NonNull IBinder transition,
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+    /** Call this when starting the open-recents animation while split-screen is active. */
+    public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) {
         setDividerVisibility(false, t);
-        return true;
+    }
+
+    /** Call this when the recents animation during split-screen finishes. */
+    public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
+            SurfaceControl.Transaction finishT) {
+        // Check if the recent transition is finished by returning to the current
+        // split, so we can restore the divider bar.
+        for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+            final WindowContainerTransaction.HierarchyOp op =
+                    finishWct.getHierarchyOps().get(i);
+            final IBinder container = op.getContainer();
+            if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+                    && (mMainStage.containsContainer(container)
+                    || mSideStage.containsContainer(container))) {
+                updateSurfaceBounds(mSplitLayout, finishT,
+                        false /* applyResizingOffset */);
+                setDividerVisibility(true, finishT);
+                return;
+            }
+        }
+
+        // Dismiss the split screen if it's not returning to split.
+        prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+        setSplitsVisible(false);
+        setDividerVisibility(false, finishT);
+        logExit(EXIT_REASON_UNKNOWN);
     }
 
     private void addDividerBarToTransition(@NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 2e86448..d094892 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -25,6 +26,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -68,14 +70,20 @@
         /** Pip was entered while handling an intent with its own remoteTransition. */
         static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
 
+        /** Recents transition while split-screen active. */
+        static final int TYPE_RECENTS_DURING_SPLIT = 4;
+
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
 
         /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
         static final int ANIM_TYPE_GOING_HOME = 1;
 
+        /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */
+        static final int ANIM_TYPE_PAIR_TO_PAIR = 1;
+
         final int mType;
-        int mAnimType = 0;
+        int mAnimType = ANIM_TYPE_DEFAULT;
         final IBinder mTransition;
 
         Transitions.TransitionHandler mLeftoversHandler = null;
@@ -167,6 +175,27 @@
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
             return handler.second;
+        } else if (mSplitHandler.isSplitActive()
+                && isOpeningType(request.getType())
+                && request.getTriggerTask() != null
+                && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME
+                        || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS)
+                && request.getRemoteTransition() != null) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+                    + "Split-Screen is active, so treat it as Mixed.");
+            Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
+                    mPlayer.dispatchRequest(transition, request, this);
+            if (handler == null) {
+                android.util.Log.e(Transitions.TAG, "   No handler for remote? This is unexpected"
+                        + ", there should at-least be RemoteHandler.");
+                return null;
+            }
+            final MixedTransition mixed = new MixedTransition(
+                    MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+            mixed.mLeftoversHandler = handler.first;
+            mActiveTransitions.add(mixed);
+            return handler.second;
         }
         return null;
     }
@@ -216,6 +245,9 @@
         } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
             return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction,
                     finishTransaction, finishCallback);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+            return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
+                    finishCallback);
         } else {
             mActiveTransitions.remove(mixed);
             throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -441,12 +473,40 @@
         return true;
     }
 
+    private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        // Split-screen is only interested in the recents transition finishing (and merging), so
+        // just wrap finish and start recents animation directly.
+        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+            mixed.mInFlightSubAnimations = 0;
+            mActiveTransitions.remove(mixed);
+            // If pair-to-pair switching, the post-recents clean-up isn't needed.
+            if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
+                wct = wct != null ? wct : new WindowContainerTransaction();
+                mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+            }
+            mSplitHandler.onTransitionAnimationComplete();
+            finishCallback.onTransitionFinished(wct, wctCB);
+        };
+        mixed.mInFlightSubAnimations = 1;
+        mSplitHandler.onRecentsInSplitAnimationStart(startTransaction);
+        final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
+                startTransaction, finishTransaction, finishCB);
+        if (!handled) {
+            mActiveTransitions.remove(mixed);
+        }
+        return handled;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         for (int i = 0; i < mActiveTransitions.size(); ++i) {
-            if (mActiveTransitions.get(i) != mergeTarget) continue;
+            if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
             MixedTransition mixed = mActiveTransitions.get(i);
             if (mixed.mInFlightSubAnimations <= 0) {
                 // Already done, so no need to end it.
@@ -474,6 +534,14 @@
                     mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
                             finishCallback);
                 }
+            } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+                if (mSplitHandler.isPendingEnter(transition)) {
+                    // Recents -> enter-split means that we are switching from one pair to
+                    // another pair.
+                    mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+                }
+                mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                        finishCallback);
             } else {
                 throw new IllegalStateException("Playing a mixed transition with unknown type? "
                         + mixed.mType);
@@ -493,6 +561,8 @@
         if (mixed == null) return;
         if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
             mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 0a9c331..8cb575c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -103,6 +103,7 @@
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
+                    0 /* inputFeatures */,
                     TYPE_APPLICATION,
                     null /* windowToken */,
                     mFocusGrantToken,
@@ -208,6 +209,7 @@
                     mDecorationSurface,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
+                    0 /* inputFeatures */,
                     touchRegion);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 3901dab..df78d92 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -35,6 +35,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -249,7 +250,7 @@
 
     @Test
     @UiThreadTest
-    public void testEnterRecents() {
+    public void testEnterRecentsAndCommit() {
         enterSplit();
 
         ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
@@ -258,27 +259,65 @@
                 .build();
 
         // Create a request to bring home forward
-        TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
+        TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+                mock(RemoteTransition.class));
         IBinder transition = mock(IBinder.class);
         WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
-
-        assertTrue(result.isEmpty());
+        // Don't handle recents opening
+        assertNull(result);
 
         // make sure we haven't made any local changes yet (need to wait until transition is ready)
         assertTrue(mStageCoordinator.isSplitScreenVisible());
 
-        // simulate the transition
-        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0)
-                .addChange(TRANSIT_TO_FRONT, homeTask)
-                .addChange(TRANSIT_TO_BACK, mMainChild)
-                .addChange(TRANSIT_TO_BACK, mSideChild)
-                .build();
+        // simulate the start of recents transition
         mMainStage.onTaskVanished(mMainChild);
         mSideStage.onTaskVanished(mSideChild);
-        mStageCoordinator.startAnimation(transition, info,
-                mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class),
-                mock(Transitions.TransitionFinishCallback.class));
+        mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+        assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+        // Make sure it cleans-up if recents doesn't restore
+        WindowContainerTransaction commitWCT = new WindowContainerTransaction();
+        mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
+                mock(SurfaceControl.Transaction.class));
+        assertFalse(mStageCoordinator.isSplitScreenVisible());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testEnterRecentsAndRestore() {
+        enterSplit();
+
+        ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_HOME)
+                .build();
+
+        // Create a request to bring home forward
+        TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+                mock(RemoteTransition.class));
+        IBinder transition = mock(IBinder.class);
+        WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
+        // Don't handle recents opening
+        assertNull(result);
+
+        // make sure we haven't made any local changes yet (need to wait until transition is ready)
+        assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+        // simulate the start of recents transition
+        mMainStage.onTaskVanished(mMainChild);
+        mSideStage.onTaskVanished(mSideChild);
+        mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+        assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+        // Make sure we remain in split after recents restores.
+        WindowContainerTransaction restoreWCT = new WindowContainerTransaction();
+        restoreWCT.reorder(mMainChild.token, true /* toTop */);
+        restoreWCT.reorder(mSideChild.token, true /* toTop */);
+        // simulate the restoreWCT being applied:
+        mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class));
+        mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class));
+        mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
+                mock(SurfaceControl.Transaction.class));
         assertTrue(mStageCoordinator.isSplitScreenVisible());
     }
 
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index b0896da..9df6822 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -91,6 +91,8 @@
 bool Properties::isLowRam = false;
 bool Properties::isSystemOrPersistent = false;
 
+float Properties::maxHdrHeadroomOn8bit = 5.f;  // TODO: Refine this number
+
 StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
 DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -150,6 +152,11 @@
 
     enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true);
 
+    auto hdrHeadroom = (float)atof(base::GetProperty(PROPERTY_8BIT_HDR_HEADROOM, "").c_str());
+    if (hdrHeadroom >= 1.f) {
+        maxHdrHeadroomOn8bit = std::min(hdrHeadroom, 100.f);
+    }
+
     // call isDrawingEnabled to force loading of the property
     isDrawingEnabled();
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ed7175e..24e206b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -218,6 +218,8 @@
 
 #define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
 
+#define PROPERTY_8BIT_HDR_HEADROOM "debug.hwui.8bit_hdr_headroom"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -321,6 +323,8 @@
     static bool isLowRam;
     static bool isSystemOrPersistent;
 
+    static float maxHdrHeadroomOn8bit;
+
     static StretchEffectBehavior getStretchEffectBehavior() {
         return stretchEffectBehavior;
     }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f10b2b2..dd781bb 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -311,7 +311,7 @@
     }
     switch (mColorMode) {
         case ColorMode::Hdr:
-            return 3.f;  // TODO: Refine this number
+            return Properties::maxHdrHeadroomOn8bit;
         case ColorMode::Hdr10:
             return 10.f;
         default:
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index c398405..c3ad767 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -53,8 +53,6 @@
     mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
 
     mLocked.resourcesLoaded = false;
-
-    mLocked.buttonState = 0;
 }
 
 MouseCursorController::~MouseCursorController() {
@@ -95,22 +93,6 @@
     setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
 }
 
-void MouseCursorController::setButtonState(int32_t buttonState) {
-#if DEBUG_MOUSE_CURSOR_UPDATES
-    ALOGD("Set button state 0x%08x", buttonState);
-#endif
-    std::scoped_lock lock(mLock);
-
-    if (mLocked.buttonState != buttonState) {
-        mLocked.buttonState = buttonState;
-    }
-}
-
-int32_t MouseCursorController::getButtonState() const {
-    std::scoped_lock lock(mLock);
-    return mLocked.buttonState;
-}
-
 void MouseCursorController::setPosition(float x, float y) {
 #if DEBUG_MOUSE_CURSOR_UPDATES
     ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 26be2a8..00dc085 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -45,8 +45,6 @@
 
     std::optional<FloatRect> getBounds() const;
     void move(float deltaX, float deltaY);
-    void setButtonState(int32_t buttonState);
-    int32_t getButtonState() const;
     void setPosition(float x, float y);
     FloatPoint getPosition() const;
     int32_t getDisplayId() const;
@@ -96,8 +94,6 @@
         PointerIconStyle requestedPointerType;
         PointerIconStyle resolvedPointerType;
 
-        int32_t buttonState;
-
         bool animating{false};
 
     } mLocked GUARDED_BY(mLock);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 544edc2..88e3519 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -136,14 +136,6 @@
     mCursorController.move(transformed.x, transformed.y);
 }
 
-void PointerController::setButtonState(int32_t buttonState) {
-    mCursorController.setButtonState(buttonState);
-}
-
-int32_t PointerController::getButtonState() const {
-    return mCursorController.getButtonState();
-}
-
 void PointerController::setPosition(float x, float y) {
     const int32_t displayId = mCursorController.getDisplayId();
     vec2 transformed;
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 6d3557c..ca14b6e 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -50,21 +50,19 @@
 
     ~PointerController() override;
 
-    virtual std::optional<FloatRect> getBounds() const;
-    virtual void move(float deltaX, float deltaY);
-    virtual void setButtonState(int32_t buttonState);
-    virtual int32_t getButtonState() const;
-    virtual void setPosition(float x, float y);
-    virtual FloatPoint getPosition() const;
-    virtual int32_t getDisplayId() const;
-    virtual void fade(Transition transition);
-    virtual void unfade(Transition transition);
-    virtual void setDisplayViewport(const DisplayViewport& viewport);
+    std::optional<FloatRect> getBounds() const override;
+    void move(float deltaX, float deltaY) override;
+    void setPosition(float x, float y) override;
+    FloatPoint getPosition() const override;
+    int32_t getDisplayId() const override;
+    void fade(Transition transition) override;
+    void unfade(Transition transition) override;
+    void setDisplayViewport(const DisplayViewport& viewport) override;
 
-    virtual void setPresentation(Presentation presentation);
-    virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                          BitSet32 spotIdBits, int32_t displayId);
-    virtual void clearSpots();
+    void setPresentation(Presentation presentation) override;
+    void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+                  BitSet32 spotIdBits, int32_t displayId) override;
+    void clearSpots() override;
 
     void updatePointerIcon(PointerIconStyle iconId);
     void setCustomPointerIcon(const SpriteIcon& icon);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 7946baee..f939f52 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -280,7 +280,7 @@
             }
             case DO_NOTIFY_TV_MESSAGE: {
                 SomeArgs args = (SomeArgs) msg.obj;
-                mTvInputSessionImpl.onTvMessageReceived((String) args.arg1, (Bundle) args.arg2);
+                mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2);
                 break;
             }
             default: {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 4e380c4..0d283fa 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1512,7 +1512,7 @@
          *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
          *             how to parse this data.
          */
-        public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type,
+        public void onTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
         }
 
@@ -2065,7 +2065,7 @@
             onAdBufferReady(buffer);
         }
 
-        void onTvMessageReceived(String type, Bundle data) {
+        void onTvMessageReceived(int type, Bundle data) {
             onTvMessage(type, data);
         }
 
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 7f4c03b..6f67d68 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -617,8 +617,6 @@
 void FilterClientCallbackImpl::getSectionEvent(jobjectArray &arr, const int size,
                                                const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/SectionEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJ)V");
 
     const DemuxFilterSectionEvent &sectionEvent = event.get<DemuxFilterEvent::Tag::section>();
     jint tableId = sectionEvent.tableId;
@@ -626,23 +624,15 @@
     jint sectionNum = sectionEvent.sectionNum;
     jlong dataLength = sectionEvent.dataLength;
 
-    jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
+    jobject obj = env->NewObject(mSectionEventClass, mSectionEventInitID, tableId, version,
+                                 sectionNum, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
                                              const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MediaEvent");
-    jmethodID eventInit = env->GetMethodID(
-            eventClazz,
-            "<init>",
-            "(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
-            "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;"
-            "Ljava/util/List;)V");
-    jfieldID eventContext = env->GetFieldID(eventClazz, "mNativeContext", "J");
 
     const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
     jobject audioDescriptor = nullptr;
@@ -650,8 +640,6 @@
     jobject presentationsJObj = JAudioPresentationInfo::asJobject(env, gAudioPresentationFields);
     switch (mediaEvent.extraMetaData.getTag()) {
         case DemuxFilterMediaEventExtraMetaData::Tag::audio: {
-            jclass adClazz = env->FindClass("android/media/tv/tuner/filter/AudioDescriptor");
-            jmethodID adInit = env->GetMethodID(adClazz, "<init>", "(BBCBBB)V");
 
             const AudioExtraMetaData &ad =
                     mediaEvent.extraMetaData.get<DemuxFilterMediaEventExtraMetaData::Tag::audio>();
@@ -662,9 +650,9 @@
             jbyte adGainFront = ad.adGainFront;
             jbyte adGainSurround = ad.adGainSurround;
 
-            audioDescriptor = env->NewObject(adClazz, adInit, adFade, adPan, versionTextTag,
-                                             adGainCenter, adGainFront, adGainSurround);
-            env->DeleteLocalRef(adClazz);
+            audioDescriptor = env->NewObject(mAudioDescriptorClass, mAudioDescriptorInitID, adFade,
+                                             adPan, versionTextTag, adGainCenter, adGainFront,
+                                             adGainSurround);
             break;
         }
         case DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations: {
@@ -705,10 +693,10 @@
         sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>();
     }
 
-    jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent,
-                                 dts, dataLength, offset, nullptr, isSecureMemory, avDataId,
-                                 mpuSequenceNumber, isPesPrivateData, sc, audioDescriptor,
-                                 presentationsJObj);
+    jobject obj = env->NewObject(mMediaEventClass, mMediaEventInitID, streamId, isPtsPresent, pts,
+                                 isDtsPresent, dts, dataLength, offset, nullptr, isSecureMemory,
+                                 avDataId, mpuSequenceNumber, isPesPrivateData, sc,
+                                 audioDescriptor, presentationsJObj);
 
     uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
     if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
@@ -717,7 +705,7 @@
                 new MediaEvent(mFilterClient, dupFromAidl(mediaEvent.avMemory),
                                mediaEvent.avDataId, dataLength + offset, obj);
         mediaEventSp->mAvHandleRefCnt++;
-        env->SetLongField(obj, eventContext, (jlong)mediaEventSp.get());
+        env->SetLongField(obj, mMediaEventFieldContextID, (jlong)mediaEventSp.get());
         mediaEventSp->incStrong(obj);
     }
 
@@ -726,32 +714,27 @@
         env->DeleteLocalRef(audioDescriptor);
     }
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
     env->DeleteLocalRef(presentationsJObj);
 }
 
 void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
                                            const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/PesEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(III)V");
 
     const DemuxFilterPesEvent &pesEvent = event.get<DemuxFilterEvent::Tag::pes>();
     jint streamId = pesEvent.streamId;
     jint dataLength = pesEvent.dataLength;
     jint mpuSequenceNumber = pesEvent.mpuSequenceNumber;
 
-    jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
+    jobject obj = env->NewObject(mPesEventClass, mPesEventInitID, streamId, dataLength,
+                                 mpuSequenceNumber);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
                                                 const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/TsRecordEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJJI)V");
 
     const DemuxFilterTsRecordEvent &tsRecordEvent = event.get<DemuxFilterEvent::Tag::tsRecord>();
     DemuxPid pid = tsRecordEvent.pid;
@@ -781,18 +764,15 @@
     jlong pts = tsRecordEvent.pts;
     jint firstMbInSlice = tsRecordEvent.firstMbInSlice;
 
-    jobject obj =
-            env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
+    jobject obj = env->NewObject(mTsRecordEventClass, mTsRecordEventInitID, jpid, ts, sc,
+                                 byteNumber, pts, firstMbInSlice);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
                                                   const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MmtpRecordEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJIJII)V");
 
     const DemuxFilterMmtpRecordEvent &mmtpRecordEvent =
             event.get<DemuxFilterEvent::Tag::mmtpRecord>();
@@ -803,18 +783,15 @@
     jint firstMbInSlice = mmtpRecordEvent.firstMbInSlice;
     jlong tsIndexMask = mmtpRecordEvent.tsIndexMask;
 
-    jobject obj = env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
-                                 mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
+    jobject obj = env->NewObject(mMmtpRecordEventClass, mMmtpRecordEventInitID, scHevcIndexMask,
+                                 byteNumber, mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
                                                 const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/DownloadEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIIII)V");
 
     const DemuxFilterDownloadEvent &downloadEvent = event.get<DemuxFilterEvent::Tag::download>();
     jint itemId = downloadEvent.itemId;
@@ -824,32 +801,27 @@
     jint lastItemFragmentIndex = downloadEvent.lastItemFragmentIndex;
     jint dataLength = downloadEvent.dataLength;
 
-    jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
-                                 itemFragmentIndex, lastItemFragmentIndex, dataLength);
+    jobject obj = env->NewObject(mDownloadEventClass, mDownloadEventInitID, itemId, downloadId,
+                                 mpuSequenceNumber, itemFragmentIndex, lastItemFragmentIndex,
+                                 dataLength);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
                                                  const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/IpPayloadEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
 
     const DemuxFilterIpPayloadEvent &ipPayloadEvent = event.get<DemuxFilterEvent::Tag::ipPayload>();
     jint dataLength = ipPayloadEvent.dataLength;
-    jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
+    jobject obj = env->NewObject(mIpPayloadEventClass, mIpPayloadEventInitID, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
                                             const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/TemiEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(JB[B)V");
 
     const DemuxFilterTemiEvent &temiEvent = event.get<DemuxFilterEvent::Tag::temi>();
     jlong pts = temiEvent.pts;
@@ -859,63 +831,53 @@
     jbyteArray array = env->NewByteArray(descrData.size());
     env->SetByteArrayRegion(array, 0, descrData.size(), reinterpret_cast<jbyte *>(&descrData[0]));
 
-    jobject obj = env->NewObject(eventClazz, eventInit, pts, descrTag, array);
+    jobject obj = env->NewObject(mTemiEventClass, mTemiEventInitID, pts, descrTag, array);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(array);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
                                                         const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/ScramblingStatusEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
 
     const DemuxFilterMonitorEvent &scramblingStatus =
             event.get<DemuxFilterEvent::Tag::monitorEvent>()
                     .get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
-    jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
+    jobject obj = env->NewObject(mScramblingStatusEventClass, mScramblingStatusEventInitID,
+                                 scramblingStatus);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
                                                    const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/IpCidChangeEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
 
     const DemuxFilterMonitorEvent &cid = event.get<DemuxFilterEvent::Tag::monitorEvent>()
                                                  .get<DemuxFilterMonitorEvent::Tag::cid>();
-    jobject obj = env->NewObject(eventClazz, eventInit, cid);
+    jobject obj = env->NewObject(mIpCidChangeEventClass, mIpCidChangeEventInitID, cid);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
                                                const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/RestartEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
 
     const int32_t &startId = event.get<DemuxFilterEvent::Tag::startId>();
-    jobject obj = env->NewObject(eventClazz, eventInit, startId);
+    jobject obj = env->NewObject(mRestartEventClass, mRestartEventInitID, startId);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
     ALOGV("FilterClientCallbackImpl::onFilterEvent");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/FilterEvent");
     jobjectArray array;
 
     if (!events.empty()) {
-        array = env->NewObjectArray(events.size(), eventClazz, nullptr);
+        array = env->NewObjectArray(events.size(), mEventClass, nullptr);
     }
 
     for (int i = 0, arraySize = 0; i < events.size(); i++) {
@@ -1004,7 +966,6 @@
               "Filter object has been freed. Ignoring callback.");
     }
     env->DeleteLocalRef(array);
-    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -1040,6 +1001,67 @@
     mSharedFilter = true;
 }
 
+FilterClientCallbackImpl::FilterClientCallbackImpl() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    ScopedLocalRef eventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/FilterEvent"));
+    ScopedLocalRef sectionEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/SectionEvent"));
+    ScopedLocalRef mediaEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/MediaEvent"));
+    ScopedLocalRef audioDescriptorClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/AudioDescriptor"));
+    ScopedLocalRef pesEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/PesEvent"));
+    ScopedLocalRef tsRecordEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/TsRecordEvent"));
+    ScopedLocalRef mmtpRecordEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/MmtpRecordEvent"));
+    ScopedLocalRef downloadEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/DownloadEvent"));
+    ScopedLocalRef ipPayloadEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/IpPayloadEvent"));
+    ScopedLocalRef temiEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/TemiEvent"));
+    ScopedLocalRef scramblingStatusEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/ScramblingStatusEvent"));
+    ScopedLocalRef ipCidChangeEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/IpCidChangeEvent"));
+    ScopedLocalRef restartEventClass =
+        ScopedLocalRef(env, env->FindClass("android/media/tv/tuner/filter/RestartEvent"));
+    mEventClass = (jclass) env->NewGlobalRef(eventClass.get());
+    mSectionEventClass = (jclass) env->NewGlobalRef(sectionEventClass.get());
+    mMediaEventClass = (jclass) env->NewGlobalRef(mediaEventClass.get());
+    mAudioDescriptorClass = (jclass) env->NewGlobalRef(audioDescriptorClass.get());
+    mPesEventClass = (jclass) env->NewGlobalRef(pesEventClass.get());
+    mTsRecordEventClass = (jclass) env->NewGlobalRef(tsRecordEventClass.get());
+    mMmtpRecordEventClass = (jclass) env->NewGlobalRef(mmtpRecordEventClass.get());
+    mDownloadEventClass = (jclass) env->NewGlobalRef(downloadEventClass.get());
+    mIpPayloadEventClass = (jclass) env->NewGlobalRef(ipPayloadEventClass.get());
+    mTemiEventClass = (jclass) env->NewGlobalRef(temiEventClass.get());
+    mScramblingStatusEventClass = (jclass) env->NewGlobalRef(scramblingStatusEventClass.get());
+    mIpCidChangeEventClass = (jclass) env->NewGlobalRef(ipCidChangeEventClass.get());
+    mRestartEventClass = (jclass) env->NewGlobalRef(restartEventClass.get());
+    mSectionEventInitID = env->GetMethodID(mSectionEventClass, "<init>", "(IIIJ)V");
+    mMediaEventInitID = env->GetMethodID(
+            mMediaEventClass,
+            "<init>",
+            "(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
+            "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;"
+            "Ljava/util/List;)V");
+    mAudioDescriptorInitID = env->GetMethodID(mAudioDescriptorClass, "<init>", "(BBCBBB)V");
+    mPesEventInitID = env->GetMethodID(mPesEventClass, "<init>", "(III)V");
+    mTsRecordEventInitID = env->GetMethodID(mTsRecordEventClass, "<init>", "(IIIJJI)V");
+    mMmtpRecordEventInitID = env->GetMethodID(mMmtpRecordEventClass, "<init>", "(IJIJII)V");
+    mDownloadEventInitID = env->GetMethodID(mDownloadEventClass, "<init>", "(IIIIII)V");
+    mIpPayloadEventInitID = env->GetMethodID(mIpPayloadEventClass, "<init>", "(I)V");
+    mTemiEventInitID = env->GetMethodID(mTemiEventClass, "<init>", "(JB[B)V");
+    mScramblingStatusEventInitID = env->GetMethodID(mScramblingStatusEventClass, "<init>", "(I)V");
+    mIpCidChangeEventInitID = env->GetMethodID(mIpCidChangeEventClass, "<init>", "(I)V");
+    mRestartEventInitID = env->GetMethodID(mRestartEventClass, "<init>", "(I)V");
+    mMediaEventFieldContextID = env->GetFieldID(mMediaEventClass, "mNativeContext", "J");
+}
+
 FilterClientCallbackImpl::~FilterClientCallbackImpl() {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     if (mFilterObj != nullptr) {
@@ -1047,6 +1069,19 @@
         mFilterObj = nullptr;
     }
     mFilterClient = nullptr;
+    env->DeleteGlobalRef(mEventClass);
+    env->DeleteGlobalRef(mSectionEventClass);
+    env->DeleteGlobalRef(mMediaEventClass);
+    env->DeleteGlobalRef(mAudioDescriptorClass);
+    env->DeleteGlobalRef(mPesEventClass);
+    env->DeleteGlobalRef(mTsRecordEventClass);
+    env->DeleteGlobalRef(mMmtpRecordEventClass);
+    env->DeleteGlobalRef(mDownloadEventClass);
+    env->DeleteGlobalRef(mIpPayloadEventClass);
+    env->DeleteGlobalRef(mTemiEventClass);
+    env->DeleteGlobalRef(mScramblingStatusEventClass);
+    env->DeleteGlobalRef(mIpCidChangeEventClass);
+    env->DeleteGlobalRef(mRestartEventClass);
 }
 
 /////////////// FrontendClientCallbackImpl ///////////////////////
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 2bb14f6..6b1b6b1 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -125,6 +125,7 @@
 };
 
 struct FilterClientCallbackImpl : public FilterClientCallback {
+    FilterClientCallbackImpl();
     ~FilterClientCallbackImpl();
     virtual void onFilterEvent(const vector<DemuxFilterEvent>& events);
     virtual void onFilterStatus(const DemuxFilterStatus status);
@@ -135,6 +136,32 @@
 private:
     jweak mFilterObj;
     sp<FilterClient> mFilterClient;
+    jclass mEventClass;
+    jclass mSectionEventClass;
+    jclass mMediaEventClass;
+    jclass mAudioDescriptorClass;
+    jclass mPesEventClass;
+    jclass mTsRecordEventClass;
+    jclass mMmtpRecordEventClass;
+    jclass mDownloadEventClass;
+    jclass mIpPayloadEventClass;
+    jclass mTemiEventClass;
+    jclass mScramblingStatusEventClass;
+    jclass mIpCidChangeEventClass;
+    jclass mRestartEventClass;
+    jmethodID mSectionEventInitID;
+    jmethodID mMediaEventInitID;
+    jmethodID mAudioDescriptorInitID;
+    jmethodID mPesEventInitID;
+    jmethodID mTsRecordEventInitID;
+    jmethodID mMmtpRecordEventInitID;
+    jmethodID mDownloadEventInitID;
+    jmethodID mIpPayloadEventInitID;
+    jmethodID mTemiEventInitID;
+    jmethodID mScramblingStatusEventInitID;
+    jmethodID mIpCidChangeEventInitID;
+    jmethodID mRestartEventInitID;
+    jfieldID mMediaEventFieldContextID;
     bool mSharedFilter;
     void getSectionEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
     void getMediaEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index dd60763..5632458 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -250,7 +250,7 @@
                 leftButton = if (totalEntriesCount > 1) {
                     {
                         ActionButton(
-                            stringResource(R.string.get_dialog_use_saved_passkey_for),
+                            stringResource(R.string.get_dialog_title_sign_in_options),
                             onMoreOptionSelected
                         )
                     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index ca88f8d..215f6b9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spa.framework.common
 
-import android.app.settings.SettingsEnums
 import android.os.Bundle
 
 // Defines the category of the log, for quick filter
@@ -32,14 +31,14 @@
 }
 
 // Defines the log events in Spa.
-enum class LogEvent(val action: Int) {
+enum class LogEvent {
     // Page related events.
-    PAGE_ENTER(SettingsEnums.PAGE_VISIBLE),
-    PAGE_LEAVE(SettingsEnums.PAGE_HIDE),
+    PAGE_ENTER,
+    PAGE_LEAVE,
 
     // Entry related events.
-    ENTRY_CLICK(SettingsEnums.ACTION_SETTINGS_TILE_CLICK),
-    ENTRY_SWITCH(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
+    ENTRY_CLICK,
+    ENTRY_SWITCH,
 }
 
 internal const val LOG_DATA_DISPLAY_NAME = "name"
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index a4c59ea..a8eeec3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -31,7 +31,7 @@
 
 /**
  * This class tracks changes for config/global/secure/system tables
- * on a per user basis and updates shared memory regions which
+ * on a per-user basis and updates shared memory regions which
  * client processes can read to determine if their local caches are
  * stale.
  */
@@ -196,7 +196,9 @@
         if (backingStore == null) {
             try {
                 if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
-                    Slog.e(LOG_TAG, "Error creating backing store - at capacity");
+                    if (DEBUG) {
+                        Slog.e(LOG_TAG, "Error creating backing store - at capacity");
+                    }
                     return null;
                 }
                 backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
@@ -256,7 +258,9 @@
                             + " on user:" + SettingsState.getUserIdFromKey(key));
                 }
             } else {
-                Slog.e(LOG_TAG, "Could not allocate generation index");
+                if (DEBUG) {
+                    Slog.e(LOG_TAG, "Could not allocate generation index");
+                }
             }
         }
         return index;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1136c11..4290ca0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -924,7 +924,7 @@
                   android:showForAllUsers="true"
                   android:finishOnTaskLaunch="true"
                   android:launchMode="singleInstance"
-                  android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
+                  android:configChanges="screenLayout|keyboard|keyboardHidden|orientation"
                   android:visibleToInstantApps="true">
         </activity>
 
@@ -946,7 +946,7 @@
                   android:showWhenLocked="true"
                   android:showForAllUsers="true"
                   android:finishOnTaskLaunch="true"
-                  android:lockTaskMode="if_whitelisted"
+                  android:lockTaskMode="always"
                   android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                   android:visibleToInstantApps="true">
         </activity>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 440c6e5..ca84265 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -58,8 +58,5 @@
         <intent>
             <action android:name="android.intent.action.VOICE_COMMAND" />
         </intent>
-        <!--intent>
-            <action android:name="android.settings.ACCESSIBILITY_SETTINGS" />
-        </intent-->
     </queries>
 </manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 00c0a0b..e73afe7 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -29,14 +29,15 @@
 import com.android.systemui.plugins.ClockProvider
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.ClockSettings
+import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.Assert
+import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
-private val TAG = ClockRegistry::class.simpleName!!
 private const val DEBUG = true
 private val KEY_TIMESTAMP = "appliedTimestamp"
 
@@ -51,7 +52,10 @@
     val handleAllUsers: Boolean,
     defaultClockProvider: ClockProvider,
     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
+    val keepAllLoaded: Boolean,
+    val subTag: String,
 ) {
+    private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
     interface ClockChangeListener {
         // Called when the active clock changes
         fun onCurrentClockChanged() {}
@@ -76,11 +80,85 @@
 
     private val pluginListener =
         object : PluginListener<ClockProviderPlugin> {
-            override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
-                connectClocks(plugin)
+            override fun onPluginAttached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
+                manager.loadPlugin()
+            }
 
-            override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
-                disconnectClocks(plugin)
+            override fun onPluginLoaded(
+                plugin: ClockProviderPlugin,
+                pluginContext: Context,
+                manager: PluginLifecycleManager<ClockProviderPlugin>
+            ) {
+                var isClockListChanged = false
+                for (clock in plugin.getClocks()) {
+                    val id = clock.clockId
+                    var isNew = false
+                    val info =
+                        availableClocks.getOrPut(id) {
+                            isNew = true
+                            ClockInfo(clock, plugin, manager)
+                        }
+
+                    if (isNew) {
+                        isClockListChanged = true
+                        onConnected(id)
+                    }
+
+                    if (manager != info.manager) {
+                        Log.e(
+                            TAG,
+                            "Clock Id conflict on load: $id is registered to another provider"
+                        )
+                        continue
+                    }
+
+                    info.provider = plugin
+                    onLoaded(id)
+                }
+
+                if (isClockListChanged) {
+                    triggerOnAvailableClocksChanged()
+                }
+                verifyLoadedProviders()
+            }
+
+            override fun onPluginUnloaded(
+                plugin: ClockProviderPlugin,
+                manager: PluginLifecycleManager<ClockProviderPlugin>
+            ) {
+                for (clock in plugin.getClocks()) {
+                    val id = clock.clockId
+                    val info = availableClocks[id]
+                    if (info?.manager != manager) {
+                        Log.e(
+                            TAG,
+                            "Clock Id conflict on unload: $id is registered to another provider"
+                        )
+                        continue
+                    }
+                    info.provider = null
+                    onUnloaded(id)
+                }
+
+                verifyLoadedProviders()
+            }
+
+            override fun onPluginDetached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
+                val removed = mutableListOf<ClockId>()
+                availableClocks.entries.removeAll {
+                    if (it.value.manager != manager) {
+                        return@removeAll false
+                    }
+
+                    removed.add(it.key)
+                    return@removeAll true
+                }
+
+                removed.forEach(::onDisconnected)
+                if (removed.size > 0) {
+                    triggerOnAvailableClocksChanged()
+                }
+            }
         }
 
     private val userSwitchObserver =
@@ -96,7 +174,8 @@
         protected set(value) {
             if (field != value) {
                 field = value
-                scope.launch(mainDispatcher) { onClockChanged { it.onCurrentClockChanged() } }
+                verifyLoadedProviders()
+                triggerOnCurrentClockChanged()
             }
         }
 
@@ -168,9 +247,36 @@
         Assert.isNotMainThread()
     }
 
-    private fun onClockChanged(func: (ClockChangeListener) -> Unit) {
-        assertMainThread()
-        clockChangeListeners.forEach(func)
+    private var isClockChanged = AtomicBoolean(false)
+    private fun triggerOnCurrentClockChanged() {
+        val shouldSchedule = isClockChanged.compareAndSet(false, true)
+        if (!shouldSchedule) {
+            return
+        }
+
+        android.util.Log.e("HAWK", "triggerOnCurrentClockChanged")
+        scope.launch(mainDispatcher) {
+            assertMainThread()
+            android.util.Log.e("HAWK", "isClockChanged")
+            isClockChanged.set(false)
+            clockChangeListeners.forEach { it.onCurrentClockChanged() }
+        }
+    }
+
+    private var isClockListChanged = AtomicBoolean(false)
+    private fun triggerOnAvailableClocksChanged() {
+        val shouldSchedule = isClockListChanged.compareAndSet(false, true)
+        if (!shouldSchedule) {
+            return
+        }
+
+        android.util.Log.e("HAWK", "triggerOnAvailableClocksChanged")
+        scope.launch(mainDispatcher) {
+            assertMainThread()
+            android.util.Log.e("HAWK", "isClockListChanged")
+            isClockListChanged.set(false)
+            clockChangeListeners.forEach { it.onAvailableClocksChanged() }
+        }
     }
 
     public fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) {
@@ -190,7 +296,12 @@
         }
 
     init {
-        connectClocks(defaultClockProvider)
+        // Register default clock designs
+        for (clock in defaultClockProvider.getClocks()) {
+            availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
+        }
+
+        // Something has gone terribly wrong if the default clock isn't present
         if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
             throw IllegalArgumentException(
                 "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
@@ -244,59 +355,87 @@
         }
     }
 
-    private fun connectClocks(provider: ClockProvider) {
-        var isAvailableChanged = false
-        val currentId = currentClockId
-        for (clock in provider.getClocks()) {
-            val id = clock.clockId
-            val current = availableClocks[id]
-            if (current != null) {
-                Log.e(
-                    TAG,
-                    "Clock Id conflict: $id is registered by both " +
-                        "${provider::class.simpleName} and ${current.provider::class.simpleName}"
-                )
-                continue
-            }
-
-            availableClocks[id] = ClockInfo(clock, provider)
-            isAvailableChanged = true
-            if (DEBUG) {
-                Log.i(TAG, "Added ${clock.clockId}")
-            }
-
-            if (currentId == id) {
-                if (DEBUG) {
-                    Log.i(TAG, "Current clock ($currentId) was connected")
-                }
-                onClockChanged { it.onCurrentClockChanged() }
-            }
+    private var isVerifying = AtomicBoolean(false)
+    private fun verifyLoadedProviders() {
+        val shouldSchedule = isVerifying.compareAndSet(false, true)
+        if (!shouldSchedule) {
+            return
         }
 
-        if (isAvailableChanged) {
-            onClockChanged { it.onAvailableClocksChanged() }
+        scope.launch(bgDispatcher) {
+            if (keepAllLoaded) {
+                // Enforce that all plugins are loaded if requested
+                for ((_, info) in availableClocks) {
+                    info.manager?.loadPlugin()
+                }
+                isVerifying.set(false)
+                return@launch
+            }
+
+            val currentClock = availableClocks[currentClockId]
+            if (currentClock == null) {
+                // Current Clock missing, load no plugins and use default
+                for ((_, info) in availableClocks) {
+                    info.manager?.unloadPlugin()
+                }
+                isVerifying.set(false)
+                return@launch
+            }
+
+            val currentManager = currentClock.manager
+            currentManager?.loadPlugin()
+
+            for ((_, info) in availableClocks) {
+                val manager = info.manager
+                if (manager != null && manager.isLoaded && currentManager != manager) {
+                    manager.unloadPlugin()
+                }
+            }
+            isVerifying.set(false)
         }
     }
 
-    private fun disconnectClocks(provider: ClockProvider) {
-        var isAvailableChanged = false
-        val currentId = currentClockId
-        for (clock in provider.getClocks()) {
-            availableClocks.remove(clock.clockId)
-            isAvailableChanged = true
-
-            if (DEBUG) {
-                Log.i(TAG, "Removed ${clock.clockId}")
-            }
-
-            if (currentId == clock.clockId) {
-                Log.w(TAG, "Current clock ($currentId) was disconnected")
-                onClockChanged { it.onCurrentClockChanged() }
-            }
+    private fun onConnected(clockId: ClockId) {
+        if (DEBUG) {
+            Log.i(TAG, "Connected $clockId")
         }
 
-        if (isAvailableChanged) {
-            onClockChanged { it.onAvailableClocksChanged() }
+        if (currentClockId == clockId) {
+            if (DEBUG) {
+                Log.i(TAG, "Current clock ($clockId) was connected")
+            }
+        }
+    }
+
+    private fun onLoaded(clockId: ClockId) {
+        if (DEBUG) {
+            Log.i(TAG, "Loaded $clockId")
+        }
+
+        if (currentClockId == clockId) {
+            Log.i(TAG, "Current clock ($clockId) was loaded")
+            triggerOnCurrentClockChanged()
+        }
+    }
+
+    private fun onUnloaded(clockId: ClockId) {
+        if (DEBUG) {
+            Log.i(TAG, "Unloaded $clockId")
+        }
+
+        if (currentClockId == clockId) {
+            Log.w(TAG, "Current clock ($clockId) was unloaded")
+            triggerOnCurrentClockChanged()
+        }
+    }
+
+    private fun onDisconnected(clockId: ClockId) {
+        if (DEBUG) {
+            Log.i(TAG, "Disconnected $clockId")
+        }
+
+        if (currentClockId == clockId) {
+            Log.w(TAG, "Current clock ($clockId) was disconnected")
         }
     }
 
@@ -345,6 +484,7 @@
 
     private data class ClockInfo(
         val metadata: ClockMetadata,
-        val provider: ClockProvider,
+        var provider: ClockProvider?,
+        val manager: PluginLifecycleManager<ClockProviderPlugin>?,
     )
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 70b5d73..b7088d5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -50,6 +50,13 @@
     boolean isPulsing();
 
     /**
+     * Is device dreaming. This method is more inclusive than
+     * {@link android.service.dreams.IDreamManager.isDreaming}, as it will return true during the
+     * dream's wake-up phase.
+     */
+    boolean isDreaming();
+
+    /**
      * Adds a state listener
      */
     void addCallback(StateListener listener);
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
new file mode 100644
index 0000000..cc6a46f
--- /dev/null
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * Provides the ability for consumers to control plugin lifecycle.
+ *
+ * @param <T> is the target plugin type
+ */
+public interface PluginLifecycleManager<T extends Plugin> {
+    /** Returns the currently loaded plugin instance (if plugin is loaded) */
+    T getPlugin();
+
+    /** returns true if the plugin is currently loaded */
+    default boolean isLoaded() {
+        return getPlugin() != null;
+    }
+
+    /**
+     * Loads and creates the plugin instance if it does not exist.
+     *
+     * This will trigger {@link PluginListener#onPluginLoaded} with the new instance if it did not
+     * already exist.
+     */
+    void loadPlugin();
+
+    /**
+     * Unloads and destroys the plugin instance if it exists.
+     *
+     * This will trigger {@link PluginListener#onPluginUnloaded} if a concrete plugin instance
+     * existed when this call was made.
+     */
+    void unloadPlugin();
+}
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
index b488d2a..c5f5032 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
@@ -17,7 +17,32 @@
 import android.content.Context;
 
 /**
- * Interface for listening to plugins being connected.
+ * Interface for listening to plugins being connected and disconnected.
+ *
+ * The call order for a plugin is
+ *  1) {@link #onPluginAttached}
+ *          Called when a new plugin is added to the device, or an existing plugin was replaced by
+ *          the package manager. Will only be called once per package manager event. If multiple
+ *          non-conflicting packages which have the same plugin interface are installed on the
+ *          device, then this method can be called multiple times with different instances of
+ *          {@link PluginLifecycleManager} (as long as `allowMultiple` was set to true when the
+ *          listener was registered with {@link PluginManager#addPluginListener}).
+ *  2) {@link #onPluginLoaded}
+ *          Called whenever a new instance of the plugin object is created and ready for use. Can be
+ *          called multiple times per {@link PluginLifecycleManager}, but will always pass a newly
+ *          created plugin object. {@link #onPluginUnloaded} with the previous plugin object will
+ *          be called before another call to {@link #onPluginLoaded} is made. This method will be
+ *          called once automatically after {@link #onPluginAttached}. Besides the initial call,
+ *          {@link #onPluginLoaded} will occur due to {@link PluginLifecycleManager#loadPlugin}.
+ *  3) {@link #onPluginUnloaded}
+ *          Called when a request to unload the plugin has been received. This can be triggered from
+ *          a related call to {@link PluginLifecycleManager#unloadPlugin} or for any reason that
+ *          {@link #onPluginDetached} would be triggered.
+ *  4) {@link #onPluginDetached}
+ *          Called when the package is removed from the device, disabled, or replaced due to an
+ *          external trigger. These are events from the android package manager.
+ *
+ * @param <T> is the target plugin type
  */
 public interface PluginListener<T extends Plugin> {
     /**
@@ -25,14 +50,69 @@
      * This may be called multiple times if multiple plugins are allowed.
      * It may also be called in the future if the plugin package changes
      * and needs to be reloaded.
+     *
+     * @deprecated Migrate to {@link #onPluginLoaded} or {@link #onPluginAttached}
      */
-    void onPluginConnected(T plugin, Context pluginContext);
+    @Deprecated
+    default void onPluginConnected(T plugin, Context pluginContext) {
+        // Optional
+    }
+
+    /**
+     * Called when the plugin is first attached to the host application. {@link #onPluginLoaded}
+     * will be automatically called as well when first attached. This may be called multiple times
+     * if multiple plugins are allowed. It may also be called in the future if the plugin package
+     * changes and needs to be reloaded. Each call to {@link #onPluginAttached} will provide a new
+     * or different {@link PluginLifecycleManager}.
+     */
+    default void onPluginAttached(PluginLifecycleManager<T> manager) {
+        // Optional
+    }
 
     /**
      * Called when a plugin has been uninstalled/updated and should be removed
      * from use.
+     *
+     * @deprecated Migrate to {@link #onPluginDetached} or {@link #onPluginUnloaded}
      */
+    @Deprecated
     default void onPluginDisconnected(T plugin) {
         // Optional.
     }
-}
+
+    /**
+     * Called when the plugin has been detached from the host application. Implementers should no
+     * longer attempt to reload it via this {@link PluginLifecycleManager}. If the package was
+     * updated and not removed, then {@link #onPluginAttached} will be called again when the updated
+     * package is available.
+     */
+    default void onPluginDetached(PluginLifecycleManager<T> manager) {
+        // Optional.
+    }
+
+    /**
+     * Called when the plugin is loaded into the host's process and is available for use. This can
+     * happen several times if clients are using {@link PluginLifecycleManager} to manipulate a
+     * plugin's load state. Each call to {@link #onPluginLoaded} will have a matched call to
+     * {@link #onPluginUnloaded} when that plugin object should no longer be used.
+     */
+    default void onPluginLoaded(
+            T plugin,
+            Context pluginContext,
+            PluginLifecycleManager<T> manager
+    ) {
+        // Optional, default to deprecated version
+        onPluginConnected(plugin, pluginContext);
+    }
+
+    /**
+     * Called when the plugin should no longer be used. Listeners should clean up all references to
+     * the relevant plugin so that it can be garbage collected. If the plugin object is required in
+     * the future a call can be made to {@link PluginLifecycleManager#loadPlugin} to create a new
+     * plugin object and trigger {@link #onPluginLoaded}.
+     */
+    default void onPluginUnloaded(T plugin, PluginLifecycleManager<T> manager) {
+        // Optional, default to deprecated version
+        onPluginDisconnected(plugin);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 9d91419..85b6e8d 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -97,7 +97,8 @@
         android:background="@drawable/qs_media_light_source"
         android:forceHasOverlappingRendering="false"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/min_clickable_item_size"
+        android:minHeight="@dimen/min_clickable_item_size"
+        android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/qs_center_guideline_padding"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/layout/multi_shade.xml b/packages/SystemUI/res/layout/multi_shade.xml
new file mode 100644
index 0000000..78ff5f0
--- /dev/null
+++ b/packages/SystemUI/res/layout/multi_shade.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<com.android.systemui.multishade.ui.view.MultiShadeView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 4abc176..fe9542b 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -110,6 +110,13 @@
         android:clipChildren="false"
         android:clipToPadding="false" />
 
+    <ViewStub
+        android:id="@+id/multi_shade_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:inflatedId="@+id/multi_shade"
+        android:layout="@layout/multi_shade" />
+
     <com.android.systemui.biometrics.AuthRippleView
         android:id="@+id/auth_ripple"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 6354752..763930d 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -30,9 +30,6 @@
 
     <bool name="flag_charging_ripple">false</bool>
 
-    <!-- Whether to show chipbar UI whenever the device is unlocked by ActiveUnlock. -->
-    <bool name="flag_active_unlock_chipbar">true</bool>
-
     <!--  Whether the user switcher chip shows in the status bar. When true, the multi user
       avatar will no longer show on the lockscreen -->
     <bool name="flag_user_switcher_chip">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a02c429..ebf0f8e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2431,7 +2431,7 @@
 
     <!-- Shows in a dialog presented to the user to authorize this app to display a Device controls
          panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
-    <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g>can choose which controls and content show here.</string>
+    <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g> can choose which controls and content show here.</string>
 
     <!-- Shows in a dialog presented to the user to authorize this app removal from a Device
          controls panel [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
index 9766514..dedf0a7 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -53,6 +53,7 @@
 fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
     val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
     val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
+    val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
 
     // disable drawing again if necessary once work is complete
     if (!HardwareRendererCompat.isDrawingEnabled()) {
@@ -61,8 +62,12 @@
     }
 
     mainExecutor.execute {
-        val forceRedrawFuture = forceRedraw()
-        forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
+        if (isRobolectric) {
+            generateBitmap(bitmapFuture)
+        } else {
+            val forceRedrawFuture = forceRedraw()
+            forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
+        }
     }
 
     return bitmapFuture
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 2d47356..f96d1e3 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -19,6 +19,7 @@
 import android.app.Activity
 import android.app.Dialog
 import android.graphics.Bitmap
+import android.os.Build
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams
@@ -26,6 +27,7 @@
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import androidx.activity.ComponentActivity
 import androidx.test.ext.junit.rules.ActivityScenarioRule
+import java.util.concurrent.TimeUnit
 import org.junit.Assert.assertEquals
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
@@ -54,14 +56,14 @@
             )
         )
     private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule)
-            .around(deviceEmulationRule)
-            .around(screenshotRule)
-            .around(activityRule)
+    private val roboRule =
+        RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule)
+    private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
+    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
 
     override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
+        val ruleToApply = if (isRobolectric) roboRule else delegateRule
+        return ruleToApply.apply(base, description)
     }
 
     protected fun takeScreenshot(
@@ -94,7 +96,12 @@
             contentView = content.getChildAt(0)
         }
 
-        return contentView?.toBitmap() ?: error("contentView is null")
+        return if (isRobolectric) {
+            contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS)
+                ?: error("timeout while trying to capture view to bitmap")
+        } else {
+            contentView?.toBitmap() ?: error("contentView is null")
+        }
     }
 
     /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
index cc3d7a8..3d05542 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
@@ -210,12 +210,12 @@
     private void onPluginConnected(PluginInstance<T> pluginInstance) {
         if (DEBUG) Log.d(TAG, "onPluginConnected");
         PluginPrefs.setHasPlugins(mContext);
-        pluginInstance.onCreate(mContext, mListener);
+        pluginInstance.onCreate();
     }
 
     private void onPluginDisconnected(PluginInstance<T> pluginInstance) {
         if (DEBUG) Log.d(TAG, "onPluginDisconnected");
-        pluginInstance.onDestroy(mListener);
+        pluginInstance.onDestroy();
     }
 
     private void queryAll() {
@@ -312,7 +312,7 @@
             try {
                 return mPluginInstanceFactory.create(
                         mContext, appInfo, component,
-                        mPluginClass);
+                        mPluginClass, mListener);
             } catch (InvalidVersionException e) {
                 reportInvalidVersion(component, component.getClassName(), e);
             }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 2f84602..016d573 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -21,13 +21,16 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginFragment;
+import com.android.systemui.plugins.PluginLifecycleManager;
 import com.android.systemui.plugins.PluginListener;
 
 import dalvik.system.PathClassLoader;
@@ -35,7 +38,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Contains a single instantiation of a Plugin.
@@ -45,42 +48,102 @@
  *
  * @param <T> The type of plugin that this contains.
  */
-public class PluginInstance<T extends Plugin> {
+public class PluginInstance<T extends Plugin> implements PluginLifecycleManager {
     private static final String TAG = "PluginInstance";
-    private static final Map<String, ClassLoader> sClassLoaders = new ArrayMap<>();
 
-    private final Context mPluginContext;
-    private final VersionInfo mVersionInfo;
+    private final Context mAppContext;
+    private final PluginListener<T> mListener;
     private final ComponentName mComponentName;
-    private final T mPlugin;
+    private final PluginFactory<T> mPluginFactory;
+
+    private Context mPluginContext;
+    private T mPlugin;
 
     /** */
-    public PluginInstance(ComponentName componentName, T plugin, Context pluginContext,
-            VersionInfo versionInfo) {
+    public PluginInstance(
+            Context appContext,
+            PluginListener<T> listener,
+            ComponentName componentName,
+            PluginFactory<T> pluginFactory,
+            @Nullable T plugin) {
+        mAppContext = appContext;
+        mListener = listener;
         mComponentName = componentName;
+        mPluginFactory = pluginFactory;
         mPlugin = plugin;
-        mPluginContext = pluginContext;
-        mVersionInfo = versionInfo;
+
+        if (mPlugin != null) {
+            mPluginContext = mPluginFactory.createPluginContext();
+        }
     }
 
     /** Alerts listener and plugin that the plugin has been created. */
-    public void onCreate(Context appContext, PluginListener<T> listener) {
-        if (!(mPlugin instanceof PluginFragment)) {
-            // Only call onCreate for plugins that aren't fragments, as fragments
-            // will get the onCreate as part of the fragment lifecycle.
-            mPlugin.onCreate(appContext, mPluginContext);
+    public void onCreate() {
+        mListener.onPluginAttached(this);
+        if (mPlugin == null) {
+            loadPlugin();
+        } else {
+            if (!(mPlugin instanceof PluginFragment)) {
+                // Only call onCreate for plugins that aren't fragments, as fragments
+                // will get the onCreate as part of the fragment lifecycle.
+                mPlugin.onCreate(mAppContext, mPluginContext);
+            }
+            mListener.onPluginLoaded(mPlugin, mPluginContext, this);
         }
-        listener.onPluginConnected(mPlugin, mPluginContext);
     }
 
     /** Alerts listener and plugin that the plugin is being shutdown. */
-    public void onDestroy(PluginListener<T> listener) {
-        listener.onPluginDisconnected(mPlugin);
+    public void onDestroy() {
+        unloadPlugin();
+        mListener.onPluginDetached(this);
+    }
+
+    /** Returns the current plugin instance (if it is loaded). */
+    @Nullable
+    public T getPlugin() {
+        return mPlugin;
+    }
+
+    /**
+     * Loads and creates the plugin if it does not exist.
+     */
+    public void loadPlugin() {
+        if (mPlugin != null) {
+            return;
+        }
+
+        mPlugin = mPluginFactory.createPlugin();
+        mPluginContext = mPluginFactory.createPluginContext();
+        if (mPlugin == null || mPluginContext == null) {
+            return;
+        }
+
+        if (!(mPlugin instanceof PluginFragment)) {
+            // Only call onCreate for plugins that aren't fragments, as fragments
+            // will get the onCreate as part of the fragment lifecycle.
+            mPlugin.onCreate(mAppContext, mPluginContext);
+        }
+        mListener.onPluginLoaded(mPlugin, mPluginContext, this);
+    }
+
+    /**
+     * Unloads and destroys the current plugin instance if it exists.
+     *
+     * This will free the associated memory if there are not other references.
+     */
+    public void unloadPlugin() {
+        if (mPlugin == null) {
+            return;
+        }
+
+        mListener.onPluginUnloaded(mPlugin, this);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onDestroy for plugins that aren't fragments, as fragments
             // will get the onDestroy as part of the fragment lifecycle.
             mPlugin.onDestroy();
         }
+        mPlugin = null;
+        mPluginContext = null;
     }
 
     /**
@@ -89,7 +152,7 @@
      * It does this by string comparison of the class names.
      **/
     public boolean containsPluginClass(Class pluginClass) {
-        return mPlugin.getClass().getName().equals(pluginClass.getName());
+        return mComponentName.getClassName().equals(pluginClass.getName());
     }
 
     public ComponentName getComponentName() {
@@ -101,7 +164,7 @@
     }
 
     public VersionInfo getVersionInfo() {
-        return mVersionInfo;
+        return mPluginFactory.checkVersion(mPlugin);
     }
 
     @VisibleForTesting
@@ -134,21 +197,20 @@
                 Context context,
                 ApplicationInfo appInfo,
                 ComponentName componentName,
-                Class<T> pluginClass)
+                Class<T> pluginClass,
+                PluginListener<T> listener)
                 throws PackageManager.NameNotFoundException, ClassNotFoundException,
                 InstantiationException, IllegalAccessException {
 
-            ClassLoader classLoader = getClassLoader(appInfo, mBaseClassLoader);
-            Context pluginContext = new PluginActionManager.PluginContextWrapper(
-                    context.createApplicationContext(appInfo, 0), classLoader);
-            Class<T> instanceClass = (Class<T>) Class.forName(
-                    componentName.getClassName(), true, classLoader);
+            PluginFactory<T> pluginFactory = new PluginFactory<T>(
+                    context, mInstanceFactory, appInfo, componentName, mVersionChecker, pluginClass,
+                    () -> getClassLoader(appInfo, mBaseClassLoader));
             // TODO: Only create the plugin before version check if we need it for
             // legacy version check.
-            T instance = (T) mInstanceFactory.create(instanceClass);
-            VersionInfo version = mVersionChecker.checkVersion(
-                    instanceClass, pluginClass, instance);
-            return new PluginInstance<T>(componentName, instance, pluginContext, version);
+            T instance = pluginFactory.createPlugin();
+            pluginFactory.checkVersion(instance);
+            return new PluginInstance<T>(
+                    context, listener, componentName, pluginFactory, instance);
         }
 
         private boolean isPluginPackagePrivileged(String packageName) {
@@ -179,9 +241,6 @@
                         + appInfo.sourceDir + ", pkg: " + appInfo.packageName);
                 return null;
             }
-            if (sClassLoaders.containsKey(appInfo.packageName)) {
-                return sClassLoaders.get(appInfo.packageName);
-            }
 
             List<String> zipPaths = new ArrayList<>();
             List<String> libPaths = new ArrayList<>();
@@ -190,13 +249,20 @@
                     TextUtils.join(File.pathSeparator, zipPaths),
                     TextUtils.join(File.pathSeparator, libPaths),
                     getParentClassLoader(baseClassLoader));
-            sClassLoaders.put(appInfo.packageName, classLoader);
             return classLoader;
         }
     }
 
     /** Class that compares a plugin class against an implementation for version matching. */
-    public static class VersionChecker {
+    public interface VersionChecker {
+        /** Compares two plugin classes. */
+        <T extends Plugin> VersionInfo checkVersion(
+                Class<T> instanceClass, Class<T> pluginClass, Plugin plugin);
+    }
+
+    /** Class that compares a plugin class against an implementation for version matching. */
+    public static class VersionCheckerImpl implements VersionChecker {
+        @Override
         /** Compares two plugin classes. */
         public <T extends Plugin> VersionInfo checkVersion(
                 Class<T> instanceClass, Class<T> pluginClass, Plugin plugin) {
@@ -204,7 +270,7 @@
             VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass);
             if (instanceVersion.hasVersionInfo()) {
                 pluginVersion.checkVersion(instanceVersion);
-            } else {
+            } else if (plugin != null) {
                 int fallbackVersion = plugin.getVersion();
                 if (fallbackVersion != pluginVersion.getDefaultVersion()) {
                     throw new VersionInfo.InvalidVersionException("Invalid legacy version", false);
@@ -225,4 +291,74 @@
             return (T) cls.newInstance();
         }
     }
+
+    /**
+     * Instanced wrapper of InstanceFactory
+     *
+     * @param <T> is the type of the plugin object to be built
+     **/
+    public static class PluginFactory<T extends Plugin> {
+        private final Context mContext;
+        private final InstanceFactory<?> mInstanceFactory;
+        private final ApplicationInfo mAppInfo;
+        private final ComponentName mComponentName;
+        private final VersionChecker mVersionChecker;
+        private final Class<T> mPluginClass;
+        private final Supplier<ClassLoader> mClassLoaderFactory;
+
+        public PluginFactory(
+                Context context,
+                InstanceFactory<?> instanceFactory,
+                ApplicationInfo appInfo,
+                ComponentName componentName,
+                VersionChecker versionChecker,
+                Class<T> pluginClass,
+                Supplier<ClassLoader> classLoaderFactory) {
+            mContext = context;
+            mInstanceFactory = instanceFactory;
+            mAppInfo = appInfo;
+            mComponentName = componentName;
+            mVersionChecker = versionChecker;
+            mPluginClass = pluginClass;
+            mClassLoaderFactory = classLoaderFactory;
+        }
+
+        /** Creates the related plugin object from the factory */
+        public T createPlugin() {
+            try {
+                ClassLoader loader = mClassLoaderFactory.get();
+                Class<T> instanceClass = (Class<T>) Class.forName(
+                        mComponentName.getClassName(), true, loader);
+                return (T) mInstanceFactory.create(instanceClass);
+            } catch (ClassNotFoundException ex) {
+                Log.e(TAG, "Failed to load plugin", ex);
+            } catch (IllegalAccessException ex) {
+                Log.e(TAG, "Failed to load plugin", ex);
+            } catch (InstantiationException ex) {
+                Log.e(TAG, "Failed to load plugin", ex);
+            }
+            return null;
+        }
+
+        /** Creates a context wrapper for the plugin */
+        public Context createPluginContext() {
+            try {
+                ClassLoader loader = mClassLoaderFactory.get();
+                return new PluginActionManager.PluginContextWrapper(
+                    mContext.createApplicationContext(mAppInfo, 0), loader);
+            } catch (NameNotFoundException ex) {
+                Log.e(TAG, "Failed to create plugin context", ex);
+            }
+            return null;
+        }
+
+        /** Check Version and create VersionInfo for instance */
+        public VersionInfo checkVersion(T instance) {
+            if (instance == null) {
+                instance = createPlugin();
+            }
+            return mVersionChecker.checkVersion(
+                    (Class<T>) instance.getClass(), mPluginClass, instance);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index b1a83fb..6e98a18 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -60,7 +60,9 @@
                 featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
                 /* handleAllUsers= */ true,
                 new DefaultClockProvider(context, layoutInflater, resources),
-                context.getString(R.string.lockscreen_clock_id_fallback));
+                context.getString(R.string.lockscreen_clock_id_fallback),
+                /* keepAllLoaded = */ false,
+                /* subTag = */ "System");
         registry.registerListeners();
         return registry;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index bf0a692..224eb1c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -20,6 +20,8 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
 import android.os.Bundle
 import android.os.RemoteException
 import android.service.dreams.IDreamManager
@@ -57,9 +59,11 @@
     private lateinit var parent: ViewGroup
     private lateinit var broadcastReceiver: BroadcastReceiver
     private var mExitToDream: Boolean = false
+    private lateinit var lastConfiguration: Configuration
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        lastConfiguration = resources.configuration
         if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
             window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
         }
@@ -92,6 +96,14 @@
         initBroadcastReceiver()
     }
 
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        if (lastConfiguration.diff(newConfig) and ActivityInfo.CONFIG_ORIENTATION != 0 ) {
+            uiController.onOrientationChange()
+        }
+        lastConfiguration = newConfig
+    }
+
     override fun onStart() {
         super.onStart()
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 0d53117..3ecf423 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -64,6 +64,8 @@
      * This element will be the one that appears when the user first opens the controls activity.
      */
     fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
+
+    fun onOrientationChange()
 }
 
 sealed class SelectedItem {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 5da86de9..ee12db8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -124,6 +124,7 @@
     }
 
     private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
+    private var selectionItem: SelectionItem? = null
     private lateinit var allStructures: List<StructureInfo>
     private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
     private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
@@ -230,6 +231,7 @@
         this.overflowMenuAdapter = null
         hidden = false
         retainCache = false
+        selectionItem = null
 
         controlActionCoordinator.activityContext = activityContext
 
@@ -272,7 +274,7 @@
         }
     }
 
-    private fun reload(parent: ViewGroup) {
+    private fun reload(parent: ViewGroup, dismissTaskView: Boolean = true) {
         if (hidden) return
 
         controlsListingController.get().removeCallback(listingCallback)
@@ -327,8 +329,8 @@
     @VisibleForTesting
     internal fun startRemovingApp(componentName: ComponentName, appName: CharSequence) {
         removeAppDialog?.cancel()
-        removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) {
-            if (!controlsController.get().removeFavorites(componentName)) {
+        removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) { shouldRemove ->
+            if (!shouldRemove || !controlsController.get().removeFavorites(componentName)) {
                 return@createRemoveAppDialog
             }
 
@@ -425,6 +427,7 @@
         } else {
             Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
         }
+        this.selectionItem = selectionItem
 
         bgExecutor.execute {
             val intent = Intent(Intent.ACTION_MAIN)
@@ -657,6 +660,7 @@
         val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
 
         val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
+        listView.removeAllViews()
         var lastRow: ViewGroup = createRow(inflater, listView)
         selectedStructure.controls.forEach {
             val key = ControlKey(selectedStructure.componentName, it.controlId)
@@ -804,6 +808,15 @@
         }
     }
 
+    override fun onOrientationChange() {
+        selectionItem?.let {
+            when (selectedItem) {
+                is SelectedItem.StructureItem -> createListView(it)
+                is SelectedItem.PanelItem -> taskViewController?.refreshBounds() ?: reload(parent)
+            }
+        } ?: reload(parent)
+    }
+
     private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup {
         val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup
         listView.addView(row)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 78e87ca..1f89c91 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -37,7 +37,7 @@
     private val activityContext: Context,
     private val uiExecutor: Executor,
     private val pendingIntent: PendingIntent,
-    private val taskView: TaskView,
+    val taskView: TaskView,
     private val hide: () -> Unit = {}
 ) {
 
@@ -108,6 +108,10 @@
             }
         }
 
+    fun refreshBounds() {
+        taskView.onLocationChanged()
+    }
+
     fun dismiss() {
         taskView.release()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 661b2b6..c0e1133 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -162,12 +162,6 @@
     val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
         releasedFlag(216, "customizable_lock_screen_quick_affordances")
 
-    /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
-    // TODO(b/256513609): Tracking Bug
-    @JvmField
-    val ACTIVE_UNLOCK_CHIPBAR =
-        resourceBooleanFlag(217, R.bool.flag_active_unlock_chipbar, "active_unlock_chipbar")
-
     /**
      * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
      * new KeyguardTransitionRepository.
@@ -284,8 +278,7 @@
     /** Enables new QS Edit Mode visual refresh */
     // TODO(b/269787742): Tracking Bug
     @JvmField
-    val ENABLE_NEW_QS_EDIT_MODE =
-        unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false)
+    val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false)
 
     // 600- status bar
 
@@ -680,4 +673,9 @@
     @JvmField
     val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
             unreleasedFlag(2602, "large_shade_granular_alpha_interpolation", teamfood = true)
+
+    // TODO(b/272805037): Tracking Bug
+    @JvmField
+    val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
+            namespace = "vpn", teamfood = false)
 }
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 fab8c06..78082c3 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
@@ -225,8 +225,10 @@
         val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
         val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
         val translationYBy = getTranslationAmount()
+        // Expand ripple before translating icon container to make sure both views have same bounds.
+        rippleController.expandToInProgressState(rippleView, iconRippleView)
         // Make the icon container view starts animation from bottom of the screen.
-        iconContainerView.translationY += rippleController.getReceiverIconSize()
+        iconContainerView.translationY = rippleController.getReceiverIconSize().toFloat()
         animateViewTranslationAndFade(
             iconContainerView,
             translationYBy = -1 * translationYBy,
@@ -235,7 +237,6 @@
         ) {
             animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO)
         }
-        rippleController.expandToInProgressState(rippleView, iconRippleView)
     }
 
     override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
@@ -293,7 +294,7 @@
 
     /** Returns the amount that the chip will be translated by in its intro animation. */
     private fun getTranslationAmount(): Float {
-        return rippleController.getRippleSize() * 0.5f
+        return rippleController.getReceiverIconSize() * 2f
     }
 
     private fun View.getAppIconView(): CachingIconView {
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
new file mode 100644
index 0000000..aecec39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.multishade.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.launch
+
+/**
+ * View that hosts the multi-shade system and acts as glue between legacy code and the
+ * implementation.
+ */
+class MultiShadeView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    FrameLayout(
+        context,
+        attrs,
+    ) {
+
+    fun init(
+        interactor: MultiShadeInteractor,
+        clock: SystemClock,
+    ) {
+        repeatWhenAttached {
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    addView(
+                        ComposeFacade.createMultiShadeView(
+                            context = context,
+                            viewModel =
+                                MultiShadeViewModel(
+                                    viewModelScope = this,
+                                    interactor = interactor,
+                                ),
+                            clock = clock,
+                        )
+                    )
+                }
+
+                // Here when destroyed.
+                removeAllViews()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
index 95f1419..fbf1a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
@@ -73,7 +73,7 @@
         return new PluginInstance.Factory(
                 PluginModule.class.getClassLoader(),
                 new PluginInstance.InstanceFactory<>(),
-                new PluginInstance.VersionChecker(),
+                new PluginInstance.VersionCheckerImpl(),
                 privilegedPlugins,
                 isDebug);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index baa812c..584d27f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -635,7 +635,8 @@
                 && mLastKeyguardAndExpanded == onKeyguardAndExpanded
                 && mLastViewHeight == currentHeight
                 && mLastHeaderTranslation == headerTranslation
-                && mSquishinessFraction == squishinessFraction) {
+                && mSquishinessFraction == squishinessFraction
+                && mLastPanelFraction == panelExpansionFraction) {
             return;
         }
         mLastHeaderTranslation = headerTranslation;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index a71e6dd..9ece72d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -26,6 +26,7 @@
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.util.leak.GarbageMonitor;
 
 import java.util.ArrayList;
@@ -33,7 +34,7 @@
 import java.util.Collection;
 import java.util.List;
 
-public interface QSHost {
+public interface QSHost extends PanelInteractor {
     String TILES_SETTING = Settings.Secure.QS_TILES;
     int POSITION_AT_END = -1;
 
@@ -57,9 +58,6 @@
     }
 
     void warn(String message, Throwable t);
-    void collapsePanels();
-    void forceCollapsePanels();
-    void openPanels();
     Context getContext();
     Context getUserContext();
     int getUserId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
new file mode 100644
index 0000000..958fa71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.dagger
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface QSHostModule {
+
+    @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost
+
+    @Module
+    companion object {
+        @Provides
+        @JvmStatic
+        fun providePanelInteractor(
+            featureFlags: FeatureFlags,
+            qsHost: QSHost,
+            panelInteractorImpl: PanelInteractorImpl
+        ): PanelInteractor {
+            return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+                panelInteractorImpl
+            } else {
+                qsHost
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 431d6e8..cfe9313 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -27,7 +27,6 @@
 import com.android.systemui.media.dagger.MediaModule;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.external.QSExternalModule;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -45,7 +44,6 @@
 
 import javax.inject.Named;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.Multibinds;
@@ -54,7 +52,13 @@
  * Module for QS dependencies
  */
 @Module(subcomponents = {QSFragmentComponent.class},
-        includes = {MediaModule.class, QSExternalModule.class, QSFlagsModule.class})
+        includes = {
+                MediaModule.class,
+                QSExternalModule.class,
+                QSFlagsModule.class,
+                QSHostModule.class
+        }
+)
 public interface QSModule {
 
     /** A map of internal QS tiles. Ensures that this can be injected even if
@@ -100,8 +104,4 @@
         manager.init();
         return manager;
     }
-
-    /** */
-    @Binds
-    QSHost provideQsHost(QSTileHost controllerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index adc7165..2083cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -40,6 +40,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -74,6 +75,7 @@
     private final CommandQueue mCommandQueue;
     private final UserTracker mUserTracker;
     private final StatusBarIconController mStatusBarIconController;
+    private final PanelInteractor mPanelInteractor;
 
     private int mMaxBound = DEFAULT_MAX_BOUND;
 
@@ -85,7 +87,8 @@
             UserTracker userTracker,
             KeyguardStateController keyguardStateController,
             CommandQueue commandQueue,
-            StatusBarIconController statusBarIconController) {
+            StatusBarIconController statusBarIconController,
+            PanelInteractor panelInteractor) {
         mHost = host;
         mKeyguardStateController = keyguardStateController;
         mContext = mHost.getContext();
@@ -96,6 +99,7 @@
         mCommandQueue = commandQueue;
         mStatusBarIconController = statusBarIconController;
         mCommandQueue.addCallback(mRequestListeningCallback);
+        mPanelInteractor = panelInteractor;
     }
 
     public Context getContext() {
@@ -255,7 +259,7 @@
         if (customTile != null) {
             verifyCaller(customTile);
             customTile.onDialogShown();
-            mHost.forceCollapsePanels();
+            mPanelInteractor.forceCollapsePanels();
             Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(true);
         }
     }
@@ -275,7 +279,7 @@
         CustomTile customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
-            mHost.forceCollapsePanels();
+            mPanelInteractor.forceCollapsePanels();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
new file mode 100644
index 0000000..260caa7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.pipeline.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import javax.inject.Inject
+
+/** Encapsulates business logic for interacting with the QS panel. */
+interface PanelInteractor {
+
+    /** Collapse the shade */
+    fun collapsePanels()
+
+    /** Collapse the shade forcefully, skipping some animations. */
+    fun forceCollapsePanels()
+
+    /** Open the Quick Settings panel */
+    fun openPanels()
+}
+
+@SysUISingleton
+class PanelInteractorImpl
+@Inject
+constructor(
+    private val centralSurfaces: Optional<CentralSurfaces>,
+) : PanelInteractor {
+    override fun collapsePanels() {
+        centralSurfaces.ifPresent { it.postAnimateCollapsePanels() }
+    }
+
+    override fun forceCollapsePanels() {
+        centralSurfaces.ifPresent { it.postAnimateForceCollapsePanels() }
+    }
+
+    override fun openPanels() {
+        centralSurfaces.ifPresent { it.postAnimateOpenPanels() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 89d402a3..27f5826 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -38,6 +38,7 @@
 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.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -52,6 +53,7 @@
 
     private final LocationController mController;
     private final KeyguardStateController mKeyguard;
+    private final PanelInteractor mPanelInteractor;
     private final Callback mCallback = new Callback();
 
     @Inject
@@ -65,12 +67,14 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger,
             LocationController locationController,
-            KeyguardStateController keyguardStateController
+            KeyguardStateController keyguardStateController,
+            PanelInteractor panelInteractor
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = locationController;
         mKeyguard = keyguardStateController;
+        mPanelInteractor = panelInteractor;
         mController.observe(this, mCallback);
         mKeyguard.observe(this, mCallback);
     }
@@ -90,7 +94,7 @@
         if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
             mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
                 final boolean wasEnabled = mState.value;
-                mHost.openPanels();
+                mPanelInteractor.openPanels();
                 mController.setLocationEnabled(!wasEnabled);
             });
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 07b50c9..65592a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -42,6 +42,7 @@
 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.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -66,6 +67,7 @@
     private final Callback mCallback = new Callback();
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final FeatureFlags mFlags;
+    private final PanelInteractor mPanelInteractor;
 
     private long mMillisUntilFinished = 0;
 
@@ -83,7 +85,8 @@
             RecordingController controller,
             KeyguardDismissUtil keyguardDismissUtil,
             KeyguardStateController keyguardStateController,
-            DialogLaunchAnimator dialogLaunchAnimator
+            DialogLaunchAnimator dialogLaunchAnimator,
+            PanelInteractor panelInteractor
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -93,6 +96,7 @@
         mKeyguardDismissUtil = keyguardDismissUtil;
         mKeyguardStateController = keyguardStateController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
+        mPanelInteractor = panelInteractor;
     }
 
     @Override
@@ -171,7 +175,7 @@
             // disable the exit animation which looks weird when it happens at the same time as the
             // shade collapsing.
             mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
-            getHost().collapsePanels();
+            mPanelInteractor.collapsePanels();
         };
 
         final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 8a3ecc6..0748bcb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -21,7 +21,6 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
-
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -713,6 +712,7 @@
     }
 
     public void startConnectionToCurrentUser() {
+        Log.v(TAG_OPS, "startConnectionToCurrentUser: connection is restarted");
         if (mHandler.getLooper() != Looper.myLooper()) {
             mHandler.post(mConnectionRunnable);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 06426b3..30865aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3581,15 +3581,12 @@
     }
 
     private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
-        // don't fling while in keyguard to avoid jump in shade expand animation
-        boolean fullyExpandedInKeyguard = mBarState == KEYGUARD && mExpandedFraction >= 1.0;
         mTrackingPointer = -1;
         mAmbientState.setSwipingUp(false);
-        if (!fullyExpandedInKeyguard && ((mTracking && mTouchSlopExceeded)
-                || Math.abs(x - mInitialExpandX) > mTouchSlop
+        if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
                 || Math.abs(y - mInitialExpandY) > mTouchSlop
                 || (!isFullyExpanded() && !isFullyCollapsed())
-                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel)) {
+                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
             mVelocityTracker.computeCurrentVelocity(1000);
             float vel = mVelocityTracker.getYVelocity();
             float vectorVel = (float) Math.hypot(
@@ -3637,9 +3634,9 @@
             if (mUpdateFlingOnLayout) {
                 mUpdateFlingVelocity = vel;
             }
-        } else if (fullyExpandedInKeyguard || (!mCentralSurfaces.isBouncerShowing()
+        } else if (!mCentralSurfaces.isBouncerShowing()
                 && !mAlternateBouncerInteractor.isVisibleState()
-                && !mKeyguardStateController.isKeyguardGoingAway())) {
+                && !mKeyguardStateController.isKeyguardGoingAway()) {
             onEmptySpaceClick();
             onTrackingStopped(true);
         }
@@ -3782,10 +3779,10 @@
                     mHeightAnimator.end();
                 }
             }
-            mQsController.setShadeExpandedHeight(mExpandedHeight);
-            mExpansionDragDownAmountPx = h;
             mExpandedFraction = Math.min(1f,
                     maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+            mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
+            mExpansionDragDownAmountPx = h;
             mAmbientState.setExpansionFraction(mExpandedFraction);
             onHeightUpdated(mExpandedHeight);
             updatePanelExpansionAndVisibility();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 2899081..a716a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -23,7 +23,6 @@
 import android.media.AudioManager;
 import android.media.session.MediaSessionLegacyHelper;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.GestureDetector;
 import android.view.InputDevice;
@@ -31,6 +30,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStub;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.AuthKeyguardMessageArea;
@@ -39,8 +39,10 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -49,6 +51,8 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
+import com.android.systemui.multishade.ui.view.MultiShadeView;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationInsetsController;
@@ -63,11 +67,13 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Controller for {@link NotificationShadeWindowView}.
@@ -115,6 +121,7 @@
                 mIsOcclusionTransitionRunning =
                     step.getTransitionState() == TransitionState.RUNNING;
             };
+    private final SystemClock mClock;
 
     @Inject
     public NotificationShadeWindowViewController(
@@ -142,7 +149,9 @@
             UdfpsOverlayInteractor udfpsOverlayInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
+            SystemClock clock) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -175,6 +184,16 @@
 
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition);
+
+        mClock = clock;
+        if (ComposeFacade.INSTANCE.isComposeAvailable()
+                && featureFlags.isEnabled(Flags.DUAL_SHADE)) {
+            final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub);
+            if (multiShadeViewStub != null) {
+                final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate();
+                multiShadeView.init(multiShadeInteractorProvider.get(), clock);
+            }
+        }
     }
 
     /**
@@ -269,7 +288,7 @@
                 mLockIconViewController.onTouchEvent(
                         ev,
                         () -> mService.wakeUpIfDozing(
-                                SystemClock.uptimeMillis(),
+                                mClock.uptimeMillis(),
                                 mView,
                                 "LOCK_ICON_TOUCH",
                                 PowerManager.WAKE_REASON_GESTURE)
@@ -453,7 +472,7 @@
 
     public void cancelCurrentTouch() {
         if (mTouchActive) {
-            final long now = SystemClock.uptimeMillis();
+            final long now = mClock.uptimeMillis();
             final MotionEvent event;
             if (mIsTrackpadGestureBackEnabled) {
                 event = MotionEvent.obtain(mDownEvent);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index df8ae50..9f46707 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -351,7 +351,6 @@
         mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
 
-        mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
         mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
     }
 
@@ -878,8 +877,9 @@
         mCollapsedOnDown = collapsedOnDown;
     }
 
-    void setShadeExpandedHeight(float shadeExpandedHeight) {
-        mShadeExpandedHeight = shadeExpandedHeight;
+    void setShadeExpansion(float expandedHeight, float expandedFraction) {
+        mShadeExpandedHeight = expandedHeight;
+        mShadeExpandedFraction = expandedFraction;
     }
 
     @VisibleForTesting
@@ -1749,11 +1749,6 @@
         return false;
     }
 
-    @VisibleForTesting
-    void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
-        mShadeExpandedFraction = event.getFraction();
-    }
-
     /**
      * Animate QS closing by flinging it.
      * If QS is expanded, it will collapse into QQS and stop.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 01e042b..c920e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -657,25 +657,6 @@
                                                 R.string.input_switch_input_language_previous),
                                         KeyEvent.KEYCODE_SPACE,
                                         KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON),
-                                        null))),
-                /* Access emoji: Meta + . */
-                new ShortcutMultiMappingInfo(
-                        context.getString(R.string.input_access_emoji),
-                        null,
-                        Arrays.asList(
-                                new ShortcutKeyGroup(new KeyboardShortcutInfo(
-                                        context.getString(R.string.input_access_emoji),
-                                        KeyEvent.KEYCODE_PERIOD,
-                                        KeyEvent.META_META_ON),
-                                        null))),
-                /* Access voice typing: Meta + V */
-                new ShortcutMultiMappingInfo(
-                        context.getString(R.string.input_access_voice_typing),
-                        null,
-                        Arrays.asList(
-                                new ShortcutKeyGroup(new KeyboardShortcutInfo(
-                                        context.getString(R.string.input_access_voice_typing),
-                                        KeyEvent.KEYCODE_V, KeyEvent.META_META_ON),
                                         null)))
         );
         return new KeyboardShortcutMultiMappingGroup(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 51c5183..cac4251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar;
 
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
-
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
 import android.app.KeyguardManager;
@@ -145,7 +144,10 @@
                     break;
                 case Intent.ACTION_USER_UNLOCKED:
                     // Start the overview connection to the launcher service
-                    mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+                    // Connect if user hasn't connected yet
+                    if (mOverviewProxyServiceLazy.get().getProxy() == null) {
+                        mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+                    }
                     break;
                 case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
                     final IntentSender intentSender = intent.getParcelableExtra(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index b9ac918..79d01b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -56,6 +56,7 @@
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.util.Compile;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -299,7 +300,7 @@
 
     @Override
     public boolean setIsDreaming(boolean isDreaming) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
+        if (Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG) {
             Log.d(TAG, "setIsDreaming:" + isDreaming);
         }
         if (mIsDreaming == isDreaming) {
@@ -321,6 +322,11 @@
     }
 
     @Override
+    public boolean isDreaming() {
+        return mIsDreaming;
+    }
+
+    @Override
     public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
         if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
             if (animated && mDozeAmountTarget == dozeAmount) {
@@ -580,6 +586,7 @@
         pw.println(" mLeaveOpenOnKeyguardHide=" + mLeaveOpenOnKeyguardHide);
         pw.println(" mKeyguardRequested=" + mKeyguardRequested);
         pw.println(" mIsDozing=" + mIsDozing);
+        pw.println(" mIsDreaming=" + mIsDreaming);
         pw.println(" mListeners{" + mListeners.size() + "}=");
         for (RankedListener rl : mListeners) {
             pw.println("    " + rl.mListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 274377f..6f4eed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -28,12 +28,9 @@
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.provider.Settings;
-import android.service.dreams.IDreamManager;
 import android.service.notification.StatusBarNotification;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -70,7 +67,6 @@
     private final KeyguardStateController mKeyguardStateController;
     private final ContentResolver mContentResolver;
     private final PowerManager mPowerManager;
-    private final IDreamManager mDreamManager;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     private final BatteryController mBatteryController;
     private final HeadsUpManager mHeadsUpManager;
@@ -112,7 +108,6 @@
     public NotificationInterruptStateProviderImpl(
             ContentResolver contentResolver,
             PowerManager powerManager,
-            IDreamManager dreamManager,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             BatteryController batteryController,
             StatusBarStateController statusBarStateController,
@@ -126,7 +121,6 @@
             UserTracker userTracker) {
         mContentResolver = contentResolver;
         mPowerManager = powerManager;
-        mDreamManager = dreamManager;
         mBatteryController = batteryController;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mStatusBarStateController = statusBarStateController;
@@ -287,7 +281,9 @@
         }
 
         // If the device is currently dreaming, then launch the FullScreenIntent
-        if (isDreaming()) {
+        // We avoid using IDreamManager#isDreaming here as that method will return false during
+        // the dream's wake-up phase.
+        if (mStatusBarStateController.isDreaming()) {
             return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_IS_DREAMING,
                     suppressedByDND);
         }
@@ -365,16 +361,6 @@
                 }
         }
     }
-
-    private boolean isDreaming() {
-        try {
-            return mDreamManager.isDreaming();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to query dream manager.", e);
-            return false;
-        }
-    }
-
     private boolean shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log) {
         StatusBarNotification sbn = entry.getSbn();
 
@@ -424,7 +410,7 @@
             return false;
         }
 
-        boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
+        boolean inUse = mPowerManager.isScreenOn() && !mStatusBarStateController.isDreaming();
 
         if (!inUse) {
             if (log) mLogger.logNoHeadsUpNotInUse(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index a0a0372..209ea41 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -59,6 +59,7 @@
 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.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.MessageRouter;
@@ -412,6 +413,7 @@
         private final GarbageMonitor gm;
         private ProcessMemInfo pmi;
         private boolean dumpInProgress;
+        private final PanelInteractor mPanelInteractor;
 
         @Inject
         public MemoryTile(
@@ -423,11 +425,13 @@
                 StatusBarStateController statusBarStateController,
                 ActivityStarter activityStarter,
                 QSLogger qsLogger,
-                GarbageMonitor monitor
+                GarbageMonitor monitor,
+                PanelInteractor panelInteractor
         ) {
             super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                     statusBarStateController, activityStarter, qsLogger);
             gm = monitor;
+            mPanelInteractor = panelInteractor;
         }
 
         @Override
@@ -457,7 +461,7 @@
                     mHandler.post(() -> {
                         dumpInProgress = false;
                         refreshState();
-                        getHost().collapsePanels();
+                        mPanelInteractor.collapsePanels();
                         mActivityStarter.postStartActivityDismissingKeyguard(shareIntent, 0);
                     });
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 3f61bf7..10757ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -347,7 +347,7 @@
         whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
         val panel = SelectedItem.PanelItem("App name", componentName)
         preferredPanelRepository.setSelectedComponent(
-                SelectedComponentRepository.SelectedComponent(panel)
+            SelectedComponentRepository.SelectedComponent(panel)
         )
         underTest.show(parent, {}, context)
         underTest.startRemovingApp(componentName, "Test App")
@@ -362,6 +362,26 @@
     }
 
     @Test
+    fun testCancelRemovingAppsDoesntRemoveFavorite() {
+        val componentName = ComponentName(context, "cls")
+        whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
+        val panel = SelectedItem.PanelItem("App name", componentName)
+        preferredPanelRepository.setSelectedComponent(
+            SelectedComponentRepository.SelectedComponent(panel)
+        )
+        underTest.show(parent, {}, context)
+        underTest.startRemovingApp(componentName, "Test App")
+
+        fakeDialogController.clickNeutral()
+
+        verify(controlsController, never()).removeFavorites(eq(componentName))
+        assertThat(underTest.getPreferredSelectedItem(emptyList())).isEqualTo(panel)
+        assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isTrue()
+        assertThat(preferredPanelRepository.getSelectedComponent())
+            .isEqualTo(SelectedComponentRepository.SelectedComponent(panel))
+    }
+
+    @Test
     fun testHideCancelsTheRemoveAppDialog() {
         val componentName = ComponentName(context, "cls")
         underTest.show(parent, {}, context)
@@ -372,10 +392,42 @@
         verify(fakeDialogController.dialog).cancel()
     }
 
+    @Test
+    fun testOnRotationWithPanelUpdateBoundsCalled() {
+        mockLayoutInflater()
+        val packageName = "pkg"
+        `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
+        val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
+        val serviceInfo = setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>()
+        verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor))
+
+        val taskView: TaskView = mock {
+            `when`(this.post(any())).thenAnswer {
+                uiExecutor.execute(it.arguments[0] as Runnable)
+                true
+            }
+        }
+
+        taskViewConsumerCaptor.value.accept(taskView)
+
+        underTest.onOrientationChange()
+        verify(taskView).onLocationChanged()
+    }
+
     private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
         val activity = ComponentName(context, "activity")
         preferredPanelRepository.setSelectedComponent(
-                SelectedComponentRepository.SelectedComponent(panel)
+            SelectedComponentRepository.SelectedComponent(panel)
         )
         return ControlsServiceInfo(panel.componentName, panel.appName, activity)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index de04ef8..9df7992 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -152,4 +152,12 @@
         listenerCaptor.value.onTaskRemovalStarted(0)
         verify(taskView).release()
     }
+
+    @Test
+    fun testOnRefreshBounds() {
+        underTest.launchTaskView()
+
+        underTest.refreshBounds()
+        verify(taskView).onLocationChanged()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 64e9a3e..7e052bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -29,6 +29,7 @@
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -41,6 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -91,6 +93,8 @@
     private TileLifecycleManager mTileLifecycleManager;
     @Mock
     private QSHost mQSHost;
+    @Mock
+    private PanelInteractor mPanelInteractor;
 
     @Before
     public void setUp() throws Exception {
@@ -107,7 +111,8 @@
         Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper());
 
         mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher,
-                mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController);
+                mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController,
+                mPanelInteractor);
     }
 
     @After
@@ -222,13 +227,37 @@
         verify(tile, never()).startActivityAndCollapse(pi);
     }
 
+    @Test
+    public void testOnStartActivityCollapsesPanel() {
+        CustomTile tile = mock(CustomTile.class);
+        ComponentName componentName = mock(ComponentName.class);
+        when(tile.getComponent()).thenReturn(componentName);
+        when(componentName.getPackageName()).thenReturn(this.getContext().getPackageName());
+        TileServiceManager manager = mTileService.getTileWrapper(tile);
+
+        mTileService.onStartActivity(manager.getToken());
+        verify(mPanelInteractor).forceCollapsePanels();
+    }
+
+    @Test
+    public void testOnShowDialogCollapsesPanel() {
+        CustomTile tile = mock(CustomTile.class);
+        ComponentName componentName = mock(ComponentName.class);
+        when(tile.getComponent()).thenReturn(componentName);
+        when(componentName.getPackageName()).thenReturn(this.getContext().getPackageName());
+        TileServiceManager manager = mTileService.getTileWrapper(tile);
+
+        mTileService.onShowDialog(manager.getToken());
+        verify(mPanelInteractor).forceCollapsePanels();
+    }
+
     private class TestTileServices extends TileServices {
         TestTileServices(QSHost host, Provider<Handler> handlerProvider,
                 BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
                 KeyguardStateController keyguardStateController, CommandQueue commandQueue,
-                StatusBarIconController statusBarIconController) {
+                StatusBarIconController statusBarIconController, PanelInteractor panelInteractor) {
             super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
-                    commandQueue, statusBarIconController);
+                    commandQueue, statusBarIconController, panelInteractor);
         }
 
         @Override
@@ -237,6 +266,8 @@
             TileServiceManager manager = mock(TileServiceManager.class);
             mManagers.add(manager);
             when(manager.isLifecycleStarted()).thenReturn(true);
+            Binder b = new Binder();
+            when(manager.getToken()).thenReturn(b);
             return manager;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
new file mode 100644
index 0000000..45783ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.pipeline.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PanelInteractorImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var centralSurfaces: CentralSurfaces
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun openPanels_callsCentralSurfaces() {
+        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+
+        underTest.openPanels()
+
+        verify(centralSurfaces).postAnimateOpenPanels()
+    }
+
+    @Test
+    fun collapsePanels_callsCentralSurfaces() {
+        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+
+        underTest.collapsePanels()
+
+        verify(centralSurfaces).postAnimateCollapsePanels()
+    }
+
+    @Test
+    fun forceCollapsePanels_callsCentralSurfaces() {
+        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+
+        underTest.forceCollapsePanels()
+
+        verify(centralSurfaces).postAnimateForceCollapsePanels()
+    }
+
+    @Test
+    fun whenOptionalEmpty_doesnThrow() {
+        val underTest = PanelInteractorImpl(Optional.empty())
+
+        underTest.openPanels()
+        underTest.collapsePanels()
+        underTest.forceCollapsePanels()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 33921c7..3642e87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -31,9 +31,12 @@
 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.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -41,6 +44,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -65,6 +69,8 @@
     private lateinit var locationController: LocationController
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var panelInteractor: PanelInteractor
 
     private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
@@ -86,7 +92,9 @@
             activityStarter,
             qsLogger,
             locationController,
-            keyguardStateController)
+            keyguardStateController,
+            panelInteractor,
+        )
     }
 
     @After
@@ -116,4 +124,18 @@
         assertThat(state.icon)
             .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_on))
     }
+
+    @Test
+    fun testClickWhenLockedWillCallOpenPanels() {
+        `when`(keyguardStateController.isMethodSecure).thenReturn(true)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
+
+        tile.handleClick(null)
+
+        val captor = argumentCaptor<Runnable>()
+        verify(activityStarter).postQSRunnableDismissingKeyguard(capture(captor))
+        captor.value.run()
+
+        verify(panelInteractor).openPanels()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 5aef758..d9ed1a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -45,6 +45,7 @@
 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.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -83,6 +84,8 @@
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock
+    private PanelInteractor mPanelInteractor;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -108,7 +111,8 @@
                 mController,
                 mKeyguardDismissUtil,
                 mKeyguardStateController,
-                mDialogLaunchAnimator
+                mDialogLaunchAnimator,
+                mPanelInteractor
         );
 
         mTile.initialize();
@@ -146,7 +150,7 @@
         assertNotNull(onStartRecordingClicked.getValue());
         onStartRecordingClicked.getValue().run();
         verify(mDialogLaunchAnimator).disableAllCurrentDialogsExitAnimations();
-        verify(mHost).collapsePanels();
+        verify(mPanelInteractor).collapsePanels();
     }
 
     // Test that the tile is active and labeled correctly when the controller is starting
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 0dc2d17..bdb0e7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -37,6 +37,9 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationInsetsController
@@ -50,8 +53,12 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -65,10 +72,12 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
 class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
+
     @Mock private lateinit var view: NotificationShadeWindowView
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
@@ -102,6 +111,8 @@
 
     private lateinit var underTest: NotificationShadeWindowViewController
 
+    private lateinit var testScope: TestScope
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -115,8 +126,12 @@
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
 
-        val featureFlags = FakeFeatureFlags();
+        val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+        featureFlags.set(Flags.DUAL_SHADE, false)
+
+        val inputProxy = MultiShadeInputProxy()
+        testScope = TestScope()
         underTest =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
@@ -144,6 +159,18 @@
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
                 featureFlags,
+                {
+                    MultiShadeInteractor(
+                        applicationScope = testScope.backgroundScope,
+                        repository =
+                            MultiShadeRepository(
+                                applicationContext = context,
+                                inputProxy = inputProxy,
+                            ),
+                        inputProxy = inputProxy,
+                    )
+                },
+                FakeSystemClock(),
             )
         underTest.setupExpandedStatusBar()
 
@@ -156,147 +183,162 @@
     // tests need to be added to test the rest of handleDispatchTouchEvent.
 
     @Test
-    fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() {
-        underTest.setStatusBarViewController(null)
+    fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(null)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        assertThat(returnVal).isFalse()
-    }
+            assertThat(returnVal).isFalse()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
-        whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
+    fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
+            whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev)
 
-        verify(phoneStatusBarViewController).sendTouchToView(ev)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController).sendTouchToView(ev)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        val downEvBelow =
-            MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
-        interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
+    fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            val downEvBelow =
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
+            interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
 
-        val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
-        whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+            val nextEvent =
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
+            whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
 
-        verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(true)
-        whenever(phoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true)
+    fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(true)
+            whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        verify(phoneStatusBarViewController).sendTouchToView(downEv)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(true)
-        // Item we're testing
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
+    fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(true)
+            // Item we're testing
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
-        assertThat(returnVal).isNull()
-    }
+            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+            assertThat(returnVal).isNull()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
-        // Item we're testing
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(false)
+    fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            // Item we're testing
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(false)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
-        assertThat(returnVal).isNull()
-    }
+            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+            assertThat(returnVal).isNull()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(true)
-        // Item we're testing
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)
+    fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(true)
+            // Item we're testing
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(true)
+    fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(true)
 
-        // Down event first
-        interactionEventHandler.handleDispatchTouchEvent(downEv)
+            // Down event first
+            interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        // Then another event
-        val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
-        whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+            // Then another event
+            val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+            whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
 
-        verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() {
-        // Down event within udfpsOverlay bounds while alternateBouncer is showing
-        whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false)
-        whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+    fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() =
+        testScope.runTest {
+            // Down event within udfpsOverlay bounds while alternateBouncer is showing
+            whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT))
+                .thenReturn(false)
+            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
 
-        // Then touch should not be intercepted
-        val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv)
-        assertThat(shouldIntercept).isFalse()
-    }
+            // Then touch should not be intercepted
+            val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
+            assertThat(shouldIntercept).isFalse()
+        }
 
     @Test
-    fun testGetBouncerContainer() {
-        Mockito.clearInvocations(view)
-        underTest.bouncerContainer
-        verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
-    }
+    fun testGetBouncerContainer() =
+        testScope.runTest {
+            Mockito.clearInvocations(view)
+            underTest.bouncerContainer
+            verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
+        }
 
     @Test
-    fun testGetKeyguardMessageArea() {
-        underTest.keyguardMessageArea
-        verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
+    fun testGetKeyguardMessageArea() =
+        testScope.runTest {
+            underTest.keyguardMessageArea
+            verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
+        }
+
+    companion object {
+        private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        private const val VIEW_BOTTOM = 100
     }
 }
-
-private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-private const val VIEW_BOTTOM = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
deleted file mode 100644
index 2797440..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ /dev/null
@@ -1,226 +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.shade;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
-import android.os.SystemClock;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.ViewGroup;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardSecurityContainerController;
-import com.android.keyguard.LockIconViewController;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
-import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.statusbar.DragDownHelper;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.NotificationInsetsController;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.stack.AmbientState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.window.StatusBarWindowStateController;
-import com.android.systemui.tuner.TunerService;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-public class NotificationShadeWindowViewTest extends SysuiTestCase {
-
-    private NotificationShadeWindowView mView;
-    private NotificationShadeWindowViewController mController;
-
-    @Mock private TunerService mTunerService;
-    @Mock private DragDownHelper mDragDownHelper;
-    @Mock private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock private ShadeController mShadeController;
-    @Mock private CentralSurfaces mCentralSurfaces;
-    @Mock private DockManager mDockManager;
-    @Mock private NotificationPanelViewController mNotificationPanelViewController;
-    @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
-    @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
-    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
-    @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
-    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
-    @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock private LockIconViewController mLockIconViewController;
-    @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-    @Mock private AmbientState mAmbientState;
-    @Mock private PulsingGestureListener mPulsingGestureListener;
-    @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
-    @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
-    @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent;
-    @Mock private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
-    @Mock private NotificationInsetsController mNotificationInsetsController;
-    @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
-    @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
-    @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
-    @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
-
-    @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
-            mInteractionEventHandlerCaptor;
-    private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mView = spy(new NotificationShadeWindowView(getContext(), null));
-        when(mView.findViewById(R.id.notification_stack_scroller))
-                .thenReturn(mNotificationStackScrollLayout);
-
-        when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class));
-        when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn(
-                mKeyguardBouncerComponent);
-        when(mKeyguardBouncerComponent.getSecurityContainerController()).thenReturn(
-                mKeyguardSecurityContainerController);
-
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        mDependency.injectTestDependency(ShadeController.class, mShadeController);
-
-        when(mDockManager.isDocked()).thenReturn(false);
-
-        when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition())
-                .thenReturn(emptyFlow());
-
-        FakeFeatureFlags featureFlags = new FakeFeatureFlags();
-        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false);
-        mController = new NotificationShadeWindowViewController(
-                mLockscreenShadeTransitionController,
-                new FalsingCollectorFake(),
-                mStatusBarStateController,
-                mDockManager,
-                mNotificationShadeDepthController,
-                mView,
-                mNotificationPanelViewController,
-                new ShadeExpansionStateManager(),
-                mNotificationStackScrollLayoutController,
-                mStatusBarKeyguardViewManager,
-                mStatusBarWindowStateController,
-                mLockIconViewController,
-                mCentralSurfaces,
-                mNotificationShadeWindowController,
-                mKeyguardUnlockAnimationController,
-                mNotificationInsetsController,
-                mAmbientState,
-                mPulsingGestureListener,
-                mKeyguardBouncerViewModel,
-                mKeyguardBouncerComponentFactory,
-                mAlternateBouncerInteractor,
-                mUdfpsOverlayInteractor,
-                mKeyguardTransitionInteractor,
-                mPrimaryBouncerToGoneTransitionViewModel,
-                featureFlags
-        );
-        mController.setupExpandedStatusBar();
-        mController.setDragDownHelper(mDragDownHelper);
-    }
-
-    @Test
-    public void testDragDownHelperCalledWhenDraggingDown() {
-        when(mDragDownHelper.isDraggingDown()).thenReturn(true);
-        long now = SystemClock.elapsedRealtime();
-        MotionEvent ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0 /* x */, 0 /* y */,
-                0 /* meta */);
-        mView.onTouchEvent(ev);
-        verify(mDragDownHelper).onTouchEvent(ev);
-        ev.recycle();
-    }
-
-    @Test
-    public void testInterceptTouchWhenShowingAltAuth() {
-        captureInteractionEventHandler();
-
-        // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        when(mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true);
-        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
-        // THEN we should intercept touch
-        assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
-    }
-
-    @Test
-    public void testNoInterceptTouch() {
-        captureInteractionEventHandler();
-
-        // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
-        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
-        // THEN we shouldn't intercept touch
-        assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
-    }
-
-    @Test
-    public void testHandleTouchEventWhenShowingAltAuth() {
-        captureInteractionEventHandler();
-
-        // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
-        // THEN we should handle the touch
-        assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class)));
-    }
-
-    private void captureInteractionEventHandler() {
-        verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture());
-        mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue();
-
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
new file mode 100644
index 0000000..5d0f408
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.shade
+
+import android.os.SystemClock
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityContainerController
+import com.android.keyguard.LockIconViewController
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.statusbar.DragDownHelper
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
+import com.android.systemui.statusbar.NotificationShadeDepthController
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class NotificationShadeWindowViewTest : SysuiTestCase() {
+
+    @Mock private lateinit var dragDownHelper: DragDownHelper
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var dockManager: DockManager
+    @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+    @Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout
+    @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
+    @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+    @Mock
+    private lateinit var notificationStackScrollLayoutController:
+        NotificationStackScrollLayoutController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
+    @Mock
+    private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+    @Mock private lateinit var lockIconViewController: LockIconViewController
+    @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+    @Mock private lateinit var ambientState: AmbientState
+    @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
+    @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
+    @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
+    @Mock
+    private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
+    @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock
+    private lateinit var primaryBouncerToGoneTransitionViewModel:
+        PrimaryBouncerToGoneTransitionViewModel
+    @Captor
+    private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
+    @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+
+    private lateinit var underTest: NotificationShadeWindowView
+    private lateinit var controller: NotificationShadeWindowViewController
+    private lateinit var interactionEventHandler: InteractionEventHandler
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = spy(NotificationShadeWindowView(context, null))
+        whenever(
+                underTest.findViewById<NotificationStackScrollLayout>(
+                    R.id.notification_stack_scroller
+                )
+            )
+            .thenReturn(notificationStackScrollLayout)
+        whenever(underTest.findViewById<FrameLayout>(R.id.keyguard_bouncer_container))
+            .thenReturn(mock())
+        whenever(keyguardBouncerComponentFactory.create(any())).thenReturn(keyguardBouncerComponent)
+        whenever(keyguardBouncerComponent.securityContainerController)
+            .thenReturn(keyguardSecurityContainerController)
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+        mDependency.injectTestDependency(ShadeController::class.java, shadeController)
+        whenever(dockManager.isDocked).thenReturn(false)
+        whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
+            .thenReturn(emptyFlow())
+
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+        featureFlags.set(Flags.DUAL_SHADE, false)
+        val inputProxy = MultiShadeInputProxy()
+        testScope = TestScope()
+        controller =
+            NotificationShadeWindowViewController(
+                lockscreenShadeTransitionController,
+                FalsingCollectorFake(),
+                statusBarStateController,
+                dockManager,
+                notificationShadeDepthController,
+                underTest,
+                notificationPanelViewController,
+                ShadeExpansionStateManager(),
+                notificationStackScrollLayoutController,
+                statusBarKeyguardViewManager,
+                statusBarWindowStateController,
+                lockIconViewController,
+                centralSurfaces,
+                notificationShadeWindowController,
+                keyguardUnlockAnimationController,
+                notificationInsetsController,
+                ambientState,
+                pulsingGestureListener,
+                keyguardBouncerViewModel,
+                keyguardBouncerComponentFactory,
+                alternateBouncerInteractor,
+                udfpsOverlayInteractor,
+                keyguardTransitionInteractor,
+                primaryBouncerToGoneTransitionViewModel,
+                featureFlags,
+                {
+                    MultiShadeInteractor(
+                        applicationScope = testScope.backgroundScope,
+                        repository =
+                            MultiShadeRepository(
+                                applicationContext = context,
+                                inputProxy = inputProxy,
+                            ),
+                        inputProxy = inputProxy,
+                    )
+                },
+                FakeSystemClock(),
+            )
+
+        controller.setupExpandedStatusBar()
+        controller.setDragDownHelper(dragDownHelper)
+    }
+
+    @Test
+    fun testDragDownHelperCalledWhenDraggingDown() =
+        testScope.runTest {
+            whenever(dragDownHelper.isDraggingDown).thenReturn(true)
+            val now = SystemClock.elapsedRealtime()
+            val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */)
+            underTest.onTouchEvent(ev)
+            verify(dragDownHelper).onTouchEvent(ev)
+            ev.recycle()
+        }
+
+    @Test
+    fun testInterceptTouchWhenShowingAltAuth() =
+        testScope.runTest {
+            captureInteractionEventHandler()
+
+            // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+            whenever(statusBarStateController.isDozing).thenReturn(false)
+            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+            whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true)
+            whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+            // THEN we should intercept touch
+            assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isTrue()
+        }
+
+    @Test
+    fun testNoInterceptTouch() =
+        testScope.runTest {
+            captureInteractionEventHandler()
+
+            // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
+            whenever(statusBarStateController.isDozing).thenReturn(false)
+            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false)
+            whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+            // THEN we shouldn't intercept touch
+            assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse()
+        }
+
+    @Test
+    fun testHandleTouchEventWhenShowingAltAuth() =
+        testScope.runTest {
+            captureInteractionEventHandler()
+
+            // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+            whenever(statusBarStateController.isDozing).thenReturn(false)
+            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+            whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+            // THEN we should handle the touch
+            assertThat(interactionEventHandler.handleTouchEvent(mock())).isTrue()
+        }
+
+    private fun captureInteractionEventHandler() {
+        verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
+        interactionEventHandler = interactionEventHandlerCaptor.value
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 15b8423..d8ffe39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -275,9 +275,7 @@
 
     @Test
     public void testPanelStaysOpenWhenClosingQs() {
-        mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
-                /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
-        mQsController.setShadeExpandedHeight(1);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
 
         float shadeExpandedHeight = mQsController.getShadeExpandedHeight();
         mQsController.animateCloseQs(false);
@@ -289,7 +287,7 @@
     public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
         mQsController.setQs(mQs);
 
-        mQsController.setShadeExpandedHeight(1f);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
         mQsController.onIntercept(
                 createMotionEvent(0, 0, ACTION_DOWN));
         mQsController.onIntercept(
@@ -303,7 +301,7 @@
         enableSplitShade(true);
         mQsController.setQs(mQs);
 
-        mQsController.setShadeExpandedHeight(1f);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
         mQsController.onIntercept(
                 createMotionEvent(0, 0, ACTION_DOWN));
         mQsController.onIntercept(
@@ -342,13 +340,8 @@
     public void handleTouch_downActionInQsArea() {
         mQsController.setQs(mQs);
         mQsController.setBarState(SHADE);
-        mQsController.onPanelExpansionChanged(
-                new ShadeExpansionChangeEvent(
-                        0.5f,
-                        true,
-                        true,
-                        0
-                ));
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 0.5f);
+
         MotionEvent event =
                 createMotionEvent(QS_FRAME_WIDTH / 2, QS_FRAME_BOTTOM / 2, ACTION_DOWN);
         mQsController.handleTouch(event, false, false);
@@ -385,7 +378,7 @@
     @Test
     public void handleTouch_isConflictingExpansionGestureSet() {
         assertThat(mQsController.isConflictingExpansionGesture()).isFalse();
-        mShadeExpansionStateManager.onPanelExpansionChanged(1f, true, false, 0f);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
         mQsController.handleTouch(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, ACTION_DOWN, 0f /* x */, 0f /* y */,
                 0 /* metaState */), false, false);
@@ -394,7 +387,7 @@
 
     @Test
     public void handleTouch_isConflictingExpansionGestureSet_cancel() {
-        mShadeExpansionStateManager.onPanelExpansionChanged(1f, true, false, 0f);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
         mQsController.handleTouch(createMotionEvent(0, 0, ACTION_DOWN), false, false);
         assertThat(mQsController.isConflictingExpansionGesture()).isTrue();
         mQsController.handleTouch(createMotionEvent(0, 0, ACTION_UP), true, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 1fdb364..374aae1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -27,13 +27,16 @@
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.ClockSettings
 import com.android.systemui.plugins.PluginListener
+import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Rule
@@ -49,6 +52,7 @@
 class ClockRegistryTest : SysuiTestCase() {
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
+    private lateinit var scheduler: TestCoroutineScheduler
     private lateinit var dispatcher: CoroutineDispatcher
     private lateinit var scope: TestScope
 
@@ -58,37 +62,38 @@
     @Mock private lateinit var mockDefaultClock: ClockController
     @Mock private lateinit var mockThumbnail: Drawable
     @Mock private lateinit var mockContentResolver: ContentResolver
+    @Mock private lateinit var mockPluginLifecycle: PluginLifecycleManager<ClockProviderPlugin>
     private lateinit var fakeDefaultProvider: FakeClockPlugin
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
 
     companion object {
-        private fun failFactory(): ClockController {
-            fail("Unexpected call to createClock")
+        private fun failFactory(clockId: ClockId): ClockController {
+            fail("Unexpected call to createClock: $clockId")
             return null!!
         }
 
-        private fun failThumbnail(): Drawable? {
-            fail("Unexpected call to getThumbnail")
+        private fun failThumbnail(clockId: ClockId): Drawable? {
+            fail("Unexpected call to getThumbnail: $clockId")
             return null
         }
     }
 
     private class FakeClockPlugin : ClockProviderPlugin {
         private val metadata = mutableListOf<ClockMetadata>()
-        private val createCallbacks = mutableMapOf<ClockId, () -> ClockController>()
-        private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
+        private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
+        private val thumbnailCallbacks = mutableMapOf<ClockId, (ClockId) -> Drawable?>()
 
         override fun getClocks() = metadata
         override fun createClock(settings: ClockSettings): ClockController =
-            createCallbacks[settings.clockId!!]!!()
-        override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
+            createCallbacks[settings.clockId!!]!!(settings.clockId!!)
+        override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
 
         fun addClock(
             id: ClockId,
             name: String,
-            create: () -> ClockController = ::failFactory,
-            getThumbnail: () -> Drawable? = ::failThumbnail
+            create: (ClockId) -> ClockController = ::failFactory,
+            getThumbnail: (ClockId) -> Drawable? = ::failThumbnail
         ): FakeClockPlugin {
             metadata.add(ClockMetadata(id, name))
             createCallbacks[id] = create
@@ -99,7 +104,8 @@
 
     @Before
     fun setUp() {
-        dispatcher = StandardTestDispatcher()
+        scheduler = TestCoroutineScheduler()
+        dispatcher = StandardTestDispatcher(scheduler)
         scope = TestScope(dispatcher)
 
         fakeDefaultProvider = FakeClockPlugin()
@@ -116,6 +122,8 @@
             isEnabled = true,
             handleAllUsers = true,
             defaultClockProvider = fakeDefaultProvider,
+            keepAllLoaded = true,
+            subTag = "Test",
         ) {
             override fun querySettings() { }
             override fun applySettings(value: ClockSettings?) {
@@ -142,8 +150,8 @@
             .addClock("clock_3", "clock 3")
             .addClock("clock_4", "clock 4")
 
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        pluginListener.onPluginConnected(plugin2, mockContext)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
         val list = registry.getClocks()
         assertEquals(
             list,
@@ -165,16 +173,18 @@
 
     @Test
     fun clockIdConflict_ErrorWithoutCrash() {
+        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
             .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
 
+        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin2 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        pluginListener.onPluginConnected(plugin2, mockContext)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
         val list = registry.getClocks()
         assertEquals(
             list,
@@ -202,8 +212,8 @@
             .addClock("clock_4", "clock 4")
 
         registry.applySettings(ClockSettings("clock_3", null))
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        pluginListener.onPluginConnected(plugin2, mockContext)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
 
         val clock = registry.createCurrentClock()
         assertEquals(mockClock, clock)
@@ -220,9 +230,9 @@
             .addClock("clock_4", "clock 4")
 
         registry.applySettings(ClockSettings("clock_3", null))
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        pluginListener.onPluginConnected(plugin2, mockContext)
-        pluginListener.onPluginDisconnected(plugin2)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle)
 
         val clock = registry.createCurrentClock()
         assertEquals(clock, mockDefaultClock)
@@ -230,15 +240,16 @@
 
     @Test
     fun pluginRemoved_clockAndListChanged() {
+        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
+        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
 
-
         var changeCallCount = 0
         var listChangeCallCount = 0
         registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
@@ -247,23 +258,38 @@
         })
 
         registry.applySettings(ClockSettings("clock_3", null))
-        assertEquals(0, changeCallCount)
+        scheduler.runCurrent()
+        assertEquals(1, changeCallCount)
         assertEquals(0, listChangeCallCount)
 
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        assertEquals(0, changeCallCount)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
+        scheduler.runCurrent()
+        assertEquals(1, changeCallCount)
         assertEquals(1, listChangeCallCount)
 
-        pluginListener.onPluginConnected(plugin2, mockContext)
-        assertEquals(1, changeCallCount)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
+        scheduler.runCurrent()
+        assertEquals(2, changeCallCount)
         assertEquals(2, listChangeCallCount)
 
-        pluginListener.onPluginDisconnected(plugin1)
-        assertEquals(1, changeCallCount)
+        pluginListener.onPluginUnloaded(plugin1, mockPluginLifecycle1)
+        scheduler.runCurrent()
+        assertEquals(2, changeCallCount)
+        assertEquals(2, listChangeCallCount)
+
+        pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle2)
+        scheduler.runCurrent()
+        assertEquals(3, changeCallCount)
+        assertEquals(2, listChangeCallCount)
+
+        pluginListener.onPluginDetached(mockPluginLifecycle1)
+        scheduler.runCurrent()
+        assertEquals(3, changeCallCount)
         assertEquals(3, listChangeCallCount)
 
-        pluginListener.onPluginDisconnected(plugin2)
-        assertEquals(2, changeCallCount)
+        pluginListener.onPluginDetached(mockPluginLifecycle2)
+        scheduler.runCurrent()
+        assertEquals(3, changeCallCount)
         assertEquals(4, listChangeCallCount)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index 05280fa..c39b29f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -79,11 +79,11 @@
     private PluginInstance<TestPlugin> mPluginInstance;
     private PluginInstance.Factory mPluginInstanceFactory = new PluginInstance.Factory(
             this.getClass().getClassLoader(),
-            new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionChecker(),
+            new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionCheckerImpl(),
             Collections.emptyList(), false) {
         @Override
         public <T extends Plugin> PluginInstance<T> create(Context context, ApplicationInfo appInfo,
-                ComponentName componentName, Class<T> pluginClass) {
+                ComponentName componentName, Class<T> pluginClass, PluginListener<T> listener) {
             return (PluginInstance<T>) mPluginInstance;
         }
     };
@@ -128,7 +128,7 @@
         createPlugin();
 
         // Verify startup lifecycle
-        verify(mPluginInstance).onCreate(mContext, mMockListener);
+        verify(mPluginInstance).onCreate();
     }
 
     @Test
@@ -140,7 +140,7 @@
         mFakeExecutor.runAllReady();
 
         // Verify shutdown lifecycle
-        verify(mPluginInstance).onDestroy(mMockListener);
+        verify(mPluginInstance).onDestroy();
     }
 
     @Test
@@ -152,9 +152,9 @@
         mFakeExecutor.runAllReady();
 
         // Verify the old one was destroyed.
-        verify(mPluginInstance).onDestroy(mMockListener);
+        verify(mPluginInstance).onDestroy();
         verify(mPluginInstance, Mockito.times(2))
-                .onCreate(mContext, mMockListener);
+                .onCreate();
     }
 
     @Test
@@ -188,7 +188,7 @@
         mFakeExecutor.runAllReady();
 
         // Verify startup lifecycle
-        verify(mPluginInstance).onCreate(mContext, mMockListener);
+        verify(mPluginInstance).onCreate();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index bb9a1e9..d5e904c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -16,11 +16,9 @@
 
 package com.android.systemui.shared.plugins;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static junit.framework.Assert.assertNull;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -31,6 +29,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginLifecycleManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 import com.android.systemui.plugins.annotations.Requires;
@@ -38,46 +37,64 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
+import java.lang.ref.WeakReference;
 import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PluginInstanceTest extends SysuiTestCase {
 
     private static final String PRIVILEGED_PACKAGE = "com.android.systemui.plugins";
-
-    @Mock
-    private TestPluginImpl mMockPlugin;
-    @Mock
-    private PluginListener<TestPlugin> mMockListener;
-    @Mock
-    private VersionInfo mVersionInfo;
-    ComponentName mTestPluginComponentName =
+    private static final ComponentName TEST_PLUGIN_COMPONENT_NAME =
             new ComponentName(PRIVILEGED_PACKAGE, TestPluginImpl.class.getName());
+
+    private FakeListener mPluginListener;
+    private VersionInfo mVersionInfo;
+    private VersionInfo.InvalidVersionException mVersionException;
+    private PluginInstance.VersionChecker mVersionChecker;
+
+    private RefCounter mCounter;
     private PluginInstance<TestPlugin> mPluginInstance;
     private PluginInstance.Factory mPluginInstanceFactory;
-
     private ApplicationInfo mAppInfo;
-    private Context mPluginContext;
-    @Mock
-    private PluginInstance.VersionChecker mVersionChecker;
+
+    // Because we're testing memory in this file, we must be careful not to assert the target
+    // objects, or capture them via mockito if we expect the garbage collector to later free them.
+    // Both JUnit and Mockito will save references and prevent these objects from being cleaned up.
+    private WeakReference<TestPluginImpl> mPlugin;
+    private WeakReference<Context> mPluginContext;
 
     @Before
     public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        mCounter = new RefCounter();
         mAppInfo = mContext.getApplicationInfo();
-        mAppInfo.packageName = mTestPluginComponentName.getPackageName();
-        when(mVersionChecker.checkVersion(any(), any(), any())).thenReturn(mVersionInfo);
+        mAppInfo.packageName = TEST_PLUGIN_COMPONENT_NAME.getPackageName();
+        mPluginListener = new FakeListener();
+        mVersionInfo = new VersionInfo();
+        mVersionChecker = new PluginInstance.VersionChecker() {
+            @Override
+            public <T extends Plugin> VersionInfo checkVersion(
+                    Class<T> instanceClass,
+                    Class<T> pluginClass,
+                    Plugin plugin
+            ) {
+                if (mVersionException != null) {
+                    throw mVersionException;
+                }
+                return mVersionInfo;
+            }
+        };
 
         mPluginInstanceFactory = new PluginInstance.Factory(
                 this.getClass().getClassLoader(),
                 new PluginInstance.InstanceFactory<TestPlugin>() {
                     @Override
                     TestPlugin create(Class cls) {
-                        return mMockPlugin;
+                        TestPluginImpl plugin = new TestPluginImpl(mCounter);
+                        mPlugin = new WeakReference<>(plugin);
+                        return plugin;
                     }
                 },
                 mVersionChecker,
@@ -85,8 +102,9 @@
                 false);
 
         mPluginInstance = mPluginInstanceFactory.create(
-                mContext, mAppInfo, mTestPluginComponentName, TestPlugin.class);
-        mPluginContext = mPluginInstance.getPluginContext();
+                mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
+                TestPlugin.class, mPluginListener);
+        mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
     }
 
     @Test
@@ -96,29 +114,51 @@
 
     @Test(expected = VersionInfo.InvalidVersionException.class)
     public void testIncorrectVersion() throws Exception {
-
         ComponentName wrongVersionTestPluginComponentName =
                 new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName());
 
-        when(mVersionChecker.checkVersion(any(), any(), any())).thenThrow(
-                new VersionInfo.InvalidVersionException("test", true));
+        mVersionException = new VersionInfo.InvalidVersionException("test", true);
 
         mPluginInstanceFactory.create(
-                mContext, mAppInfo, wrongVersionTestPluginComponentName, TestPlugin.class);
+                mContext, mAppInfo, wrongVersionTestPluginComponentName,
+                TestPlugin.class, mPluginListener);
     }
 
     @Test
     public void testOnCreate() {
-        mPluginInstance.onCreate(mContext, mMockListener);
-        verify(mMockPlugin).onCreate(mContext, mPluginContext);
-        verify(mMockListener).onPluginConnected(mMockPlugin, mPluginContext);
+        mPluginInstance.onCreate();
+        assertEquals(1, mPluginListener.mAttachedCount);
+        assertEquals(1, mPluginListener.mLoadCount);
+        assertEquals(mPlugin.get(), mPluginInstance.getPlugin());
+        assertInstances(1, 1);
     }
 
     @Test
     public void testOnDestroy() {
-        mPluginInstance.onDestroy(mMockListener);
-        verify(mMockListener).onPluginDisconnected(mMockPlugin);
-        verify(mMockPlugin).onDestroy();
+        mPluginInstance.onDestroy();
+        assertEquals(1, mPluginListener.mDetachedCount);
+        assertEquals(1, mPluginListener.mUnloadCount);
+        assertNull(mPluginInstance.getPlugin());
+        assertInstances(0, -1); // Destroyed but never created
+    }
+
+    @Test
+    public void testOnRepeatedlyLoadUnload_PluginFreed() {
+        mPluginInstance.onCreate();
+        mPluginInstance.loadPlugin();
+        assertInstances(1, 1);
+
+        mPluginInstance.unloadPlugin();
+        assertNull(mPluginInstance.getPlugin());
+        assertInstances(0, 0);
+
+        mPluginInstance.loadPlugin();
+        assertInstances(1, 1);
+
+        mPluginInstance.unloadPlugin();
+        mPluginInstance.onDestroy();
+        assertNull(mPluginInstance.getPlugin());
+        assertInstances(0, 0);
     }
 
     // This target class doesn't matter, it just needs to have a Requires to hit the flow where
@@ -129,10 +169,103 @@
         String ACTION = "testAction";
     }
 
+    public void assertInstances(Integer allocated, Integer created) {
+        // Run the garbage collector to finalize and deallocate outstanding
+        // instances. Since the GC doesn't always appear to want to run
+        // completely when we ask, we ask it 10 times in a short loop.
+        for (int i = 0; i < 10; i++) {
+            System.runFinalization();
+            System.gc();
+        }
+
+        mCounter.assertInstances(allocated, created);
+    }
+
+    public static class RefCounter {
+        public final AtomicInteger mAllocatedInstances = new AtomicInteger();
+        public final AtomicInteger mCreatedInstances = new AtomicInteger();
+
+        public void assertInstances(Integer allocated, Integer created) {
+            if (allocated != null) {
+                assertEquals(allocated.intValue(), mAllocatedInstances.get());
+            }
+            if (created != null) {
+                assertEquals(created.intValue(), mCreatedInstances.get());
+            }
+        }
+    }
+
     @Requires(target = TestPlugin.class, version = TestPlugin.VERSION)
     public static class TestPluginImpl implements TestPlugin {
+        public final RefCounter mCounter;
+        public TestPluginImpl(RefCounter counter) {
+            mCounter = counter;
+            mCounter.mAllocatedInstances.getAndIncrement();
+        }
+
+        @Override
+        public void finalize() {
+            mCounter.mAllocatedInstances.getAndDecrement();
+        }
+
         @Override
         public void onCreate(Context sysuiContext, Context pluginContext) {
+            mCounter.mCreatedInstances.getAndIncrement();
+        }
+
+        @Override
+        public void onDestroy() {
+            mCounter.mCreatedInstances.getAndDecrement();
+        }
+    }
+
+    public class FakeListener implements PluginListener<TestPlugin> {
+        public int mAttachedCount = 0;
+        public int mDetachedCount = 0;
+        public int mLoadCount = 0;
+        public int mUnloadCount = 0;
+
+        @Override
+        public void onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
+            mAttachedCount++;
+            assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+        }
+
+        @Override
+        public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) {
+            mDetachedCount++;
+            assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+        }
+
+        @Override
+        public void onPluginLoaded(
+                TestPlugin plugin,
+                Context pluginContext,
+                PluginLifecycleManager<TestPlugin> manager
+        ) {
+            mLoadCount++;
+            TestPlugin expectedPlugin = PluginInstanceTest.this.mPlugin.get();
+            if (expectedPlugin != null) {
+                assertEquals(expectedPlugin, plugin);
+            }
+            Context expectedContext = PluginInstanceTest.this.mPluginContext.get();
+            if (expectedContext != null) {
+                assertEquals(expectedContext, pluginContext);
+            }
+            assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+        }
+
+        @Override
+        public void onPluginUnloaded(
+                TestPlugin plugin,
+                PluginLifecycleManager<TestPlugin> manager
+        ) {
+            mUnloadCount++;
+            TestPlugin expectedPlugin = PluginInstanceTest.this.mPlugin.get();
+            if (expectedPlugin != null) {
+                assertEquals(expectedPlugin, plugin);
+            }
+            assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index e6f272b..3327e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -167,4 +167,13 @@
         controller.setIsDreaming(false)
         verify(listener).onDreamingChanged(false)
     }
+
+    @Test
+    fun testSetDreamState_getterReturnsCurrentState() {
+        controller.setIsDreaming(true)
+        assertTrue(controller.isDreaming())
+
+        controller.setIsDreaming(false)
+        assertFalse(controller.isDreaming())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 8acf507..653b0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -54,7 +54,6 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.service.dreams.IDreamManager;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -94,8 +93,6 @@
     @Mock
     PowerManager mPowerManager;
     @Mock
-    IDreamManager mDreamManager;
-    @Mock
     AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     @Mock
     StatusBarStateController mStatusBarStateController;
@@ -133,7 +130,6 @@
                 new NotificationInterruptStateProviderImpl(
                         mContext.getContentResolver(),
                         mPowerManager,
-                        mDreamManager,
                         mAmbientDisplayConfiguration,
                         mBatteryController,
                         mStatusBarStateController,
@@ -157,7 +153,7 @@
         when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
 
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mPowerManager.isScreenOn()).thenReturn(true);
     }
 
@@ -359,7 +355,7 @@
 
         // Also not in use if screen is on but we're showing screen saver / "dreaming"
         when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.isDreaming()).thenReturn(true);
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
 
@@ -539,7 +535,7 @@
     public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         when(mPowerManager.isInteractive()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -558,7 +554,7 @@
                 .setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
                 .build();
         when(mPowerManager.isInteractive()).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -577,7 +573,7 @@
                 .setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
                 .build();
         when(mPowerManager.isInteractive()).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -599,7 +595,7 @@
     public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -621,7 +617,7 @@
     public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
         when(mPowerManager.isInteractive()).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.isDreaming()).thenReturn(true);
         when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -651,7 +647,7 @@
     public void testShouldFullScreen_notInteractive() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -673,7 +669,7 @@
     public void testShouldFullScreen_isDreaming() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.isDreaming()).thenReturn(true);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -695,7 +691,7 @@
     public void testShouldFullScreen_onKeyguard() throws RemoteException {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -718,7 +714,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
         when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
 
         assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
@@ -735,7 +731,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
         when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
         when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
 
@@ -754,7 +750,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
         when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
         when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -775,7 +771,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
         when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
         when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -800,7 +796,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
         when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
         when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -821,7 +817,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
         when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
         when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -846,7 +842,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
         when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
         when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
         when(mKeyguardStateController.isShowing()).thenReturn(false);
@@ -892,7 +888,7 @@
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         when(mPowerManager.isInteractive()).thenReturn(true);
         when(mPowerManager.isScreenOn()).thenReturn(true);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(SHADE);
         when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
         when(mKeyguardStateController.isShowing()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 031c17f..7db2197 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -347,7 +347,6 @@
         mNotificationInterruptStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
                         mPowerManager,
-                        mDreamManager,
                         mAmbientDisplayConfiguration,
                         mStatusBarStateController,
                         mKeyguardStateController,
@@ -730,7 +729,7 @@
     public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception {
         when(mPowerManager.isScreenOn()).thenReturn(true);
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
 
         Notification n = new Notification.Builder(getContext(), "a")
                 .setGroup("a")
@@ -753,7 +752,7 @@
     public void testShouldHeadsUp_suppressedGroupSummary() throws Exception {
         when(mPowerManager.isScreenOn()).thenReturn(true);
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
 
         Notification n = new Notification.Builder(getContext(), "a")
                 .setGroup("a")
@@ -776,7 +775,7 @@
     public void testShouldHeadsUp_suppressedHeadsUp() throws Exception {
         when(mPowerManager.isScreenOn()).thenReturn(true);
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
 
@@ -797,7 +796,7 @@
     public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception {
         when(mPowerManager.isScreenOn()).thenReturn(true);
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.isDreaming()).thenReturn(false);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
 
@@ -1400,7 +1399,6 @@
         TestableNotificationInterruptStateProviderImpl(
                 ContentResolver contentResolver,
                 PowerManager powerManager,
-                IDreamManager dreamManager,
                 AmbientDisplayConfiguration ambientDisplayConfiguration,
                 StatusBarStateController controller,
                 KeyguardStateController keyguardStateController,
@@ -1415,7 +1413,6 @@
             super(
                     contentResolver,
                     powerManager,
-                    dreamManager,
                     ambientDisplayConfiguration,
                     batteryController,
                     controller,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 12e58c9..ee4e00b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -343,7 +343,6 @@
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
                         mock(PowerManager.class),
-                        mock(IDreamManager.class),
                         mock(AmbientDisplayConfiguration.class),
                         mock(StatusBarStateController.class),
                         mock(KeyguardStateController.class),
@@ -1246,6 +1245,24 @@
         // Show the menu
         stackView.showManageMenu(true);
         assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertTrue(stackView.isManageMenuSettingsVisible());
+    }
+
+    @Test
+    public void testShowManageMenuChangesSysuiState_appBubble() {
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+        assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+        // Show the menu
+        stackView.showManageMenu(true);
+        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertFalse(stackView.isManageMenuSettingsVisible());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index ceee0bc..4e14bbf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -20,7 +20,6 @@
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.service.dreams.IDreamManager;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -39,7 +38,6 @@
     TestableNotificationInterruptStateProviderImpl(
             ContentResolver contentResolver,
             PowerManager powerManager,
-            IDreamManager dreamManager,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             StatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
@@ -53,7 +51,6 @@
             UserTracker userTracker) {
         super(contentResolver,
                 powerManager,
-                dreamManager,
                 ambientDisplayConfiguration,
                 batteryController,
                 statusBarStateController,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7550196..b4e75e1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9351,7 +9351,9 @@
 
                 String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
                 String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
-                int lines = Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
+                int lines = Build.IS_USER
+                        ? 0
+                        : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
                 int dropboxMaxSize = Settings.Global.getInt(
                         mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
                 int maxDataFileSize = dropboxMaxSize - sb.length()
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e403861..3e86c45 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1372,8 +1372,8 @@
         intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         if (mMonitorRotation) {
             RotationHelper.init(mContext, mAudioHandler,
-                    rotationParam -> onRotationUpdate(rotationParam),
-                    foldParam -> onFoldUpdate(foldParam));
+                    rotation -> onRotationUpdate(rotation),
+                    foldState -> onFoldStateUpdate(foldState));
         }
 
         intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
@@ -1515,16 +1515,20 @@
 
     //-----------------------------------------------------------------
     // rotation/fold updates coming from RotationHelper
-    void onRotationUpdate(String rotationParameter) {
+    void onRotationUpdate(Integer rotation) {
+        mSpatializerHelper.setDisplayOrientation((float) (rotation * Math.PI / 180.));
         // use REPLACE as only the last rotation matters
+        final String rotationParameter = "rotation=" + rotation;
         sendMsg(mAudioHandler, MSG_ROTATION_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
                 /*obj*/ rotationParameter, /*delay*/ 0);
     }
 
-    void onFoldUpdate(String foldParameter) {
+    void onFoldStateUpdate(Boolean foldState) {
+        mSpatializerHelper.setFoldState(foldState);
         // use REPLACE as only the last fold state matters
+        final String foldStateParameter = "device_folded=" + (foldState ? "on" : "off");
         sendMsg(mAudioHandler, MSG_FOLD_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
-                /*obj*/ foldParameter, /*delay*/ 0);
+                /*obj*/ foldStateParameter, /*delay*/ 0);
     }
 
     //-----------------------------------------------------------------
@@ -1740,6 +1744,9 @@
         mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
         mSoundDoseHelper.reset();
 
+        // Restore rotation information.
+        RotationHelper.forceUpdate();
+
         onIndicateSystemReady();
         // indicate the end of reconfiguration phase to audio HAL
         AudioSystem.setParameters("restarting=false");
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index 5cdf58b..394e4af 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -55,14 +55,14 @@
     private static AudioDisplayListener sDisplayListener;
     private static FoldStateListener sFoldStateListener;
     /** callback to send rotation updates to AudioSystem */
-    private static Consumer<String> sRotationUpdateCb;
+    private static Consumer<Integer> sRotationCallback;
     /** callback to send folded state updates to AudioSystem */
-    private static Consumer<String> sFoldUpdateCb;
+    private static Consumer<Boolean> sFoldStateCallback;
 
     private static final Object sRotationLock = new Object();
     private static final Object sFoldStateLock = new Object();
-    private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock
-    private static boolean sDeviceFold = true; // R/W synchronized on sFoldStateLock
+    private static Integer sRotation = null; // R/W synchronized on sRotationLock
+    private static Boolean sFoldState = null; // R/W synchronized on sFoldStateLock
 
     private static Context sContext;
     private static Handler sHandler;
@@ -73,15 +73,15 @@
      * - sContext != null
      */
     static void init(Context context, Handler handler,
-            Consumer<String> rotationUpdateCb, Consumer<String> foldUpdateCb) {
+            Consumer<Integer> rotationCallback, Consumer<Boolean> foldStateCallback) {
         if (context == null) {
             throw new IllegalArgumentException("Invalid null context");
         }
         sContext = context;
         sHandler = handler;
         sDisplayListener = new AudioDisplayListener();
-        sRotationUpdateCb = rotationUpdateCb;
-        sFoldUpdateCb = foldUpdateCb;
+        sRotationCallback = rotationCallback;
+        sFoldStateCallback = foldStateCallback;
         enable();
     }
 
@@ -112,9 +112,9 @@
         int newRotation = DisplayManagerGlobal.getInstance()
                 .getDisplayInfo(Display.DEFAULT_DISPLAY).rotation;
         synchronized(sRotationLock) {
-            if (newRotation != sDeviceRotation) {
-                sDeviceRotation = newRotation;
-                publishRotation(sDeviceRotation);
+            if (sRotation == null || sRotation != newRotation) {
+                sRotation = newRotation;
+                publishRotation(sRotation);
             }
         }
     }
@@ -123,43 +123,52 @@
         if (DEBUG_ROTATION) {
             Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)");
         }
-        String rotationParam;
+        int rotationDegrees;
         switch (rotation) {
             case Surface.ROTATION_0:
-                rotationParam = "rotation=0";
+                rotationDegrees = 0;
                 break;
             case Surface.ROTATION_90:
-                rotationParam = "rotation=90";
+                rotationDegrees = 90;
                 break;
             case Surface.ROTATION_180:
-                rotationParam = "rotation=180";
+                rotationDegrees = 180;
                 break;
             case Surface.ROTATION_270:
-                rotationParam = "rotation=270";
+                rotationDegrees = 270;
                 break;
             default:
                 Log.e(TAG, "Unknown device rotation");
-                rotationParam = null;
+                rotationDegrees = -1;
         }
-        if (rotationParam != null) {
-            sRotationUpdateCb.accept(rotationParam);
+        if (rotationDegrees != -1) {
+            sRotationCallback.accept(rotationDegrees);
         }
     }
 
     /**
      * publish the change of device folded state if any.
      */
-    static void updateFoldState(boolean newFolded) {
+    static void updateFoldState(boolean foldState) {
         synchronized (sFoldStateLock) {
-            if (sDeviceFold != newFolded) {
-                sDeviceFold = newFolded;
-                String foldParam;
-                if (newFolded) {
-                    foldParam = "device_folded=on";
-                } else {
-                    foldParam = "device_folded=off";
-                }
-                sFoldUpdateCb.accept(foldParam);
+            if (sFoldState == null || sFoldState != foldState) {
+                sFoldState = foldState;
+                sFoldStateCallback.accept(foldState);
+            }
+        }
+    }
+
+    /**
+     *  forceUpdate is called when audioserver restarts.
+     */
+    static void forceUpdate() {
+        synchronized (sRotationLock) {
+            sRotation = null;
+        }
+        updateOrientation(); // We will get at least one orientation update now.
+        synchronized (sFoldStateLock) {
+            if (sFoldState  != null) {
+                sFoldStateCallback.accept(sFoldState);
             }
         }
     }
@@ -185,4 +194,4 @@
             updateOrientation();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 3ea4f4f..8f54e45 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1066,7 +1066,7 @@
         if (transform.length != 6) {
             throw new IllegalArgumentException("invalid array size" + transform.length);
         }
-        if (!checkSpatForHeadTracking("setGlobalTransform")) {
+        if (!checkSpatializerForHeadTracking("setGlobalTransform")) {
             return;
         }
         try {
@@ -1077,7 +1077,7 @@
     }
 
     synchronized void recenterHeadTracker() {
-        if (!checkSpatForHeadTracking("recenterHeadTracker")) {
+        if (!checkSpatializerForHeadTracking("recenterHeadTracker")) {
             return;
         }
         try {
@@ -1087,8 +1087,30 @@
         }
     }
 
+    synchronized void setDisplayOrientation(float displayOrientation) {
+        if (!checkSpatializer("setDisplayOrientation")) {
+            return;
+        }
+        try {
+            mSpat.setDisplayOrientation(displayOrientation);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling setDisplayOrientation", e);
+        }
+    }
+
+    synchronized void setFoldState(boolean folded) {
+        if (!checkSpatializer("setFoldState")) {
+            return;
+        }
+        try {
+            mSpat.setFoldState(folded);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling setFoldState", e);
+        }
+    }
+
     synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
-        if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) {
+        if (!checkSpatializerForHeadTracking("setDesiredHeadTrackingMode")) {
             return;
         }
         if (mode != Spatializer.HEAD_TRACKING_MODE_DISABLED) {
@@ -1186,7 +1208,7 @@
         return mHeadTrackerAvailable;
     }
 
-    private boolean checkSpatForHeadTracking(String funcName) {
+    private boolean checkSpatializer(String funcName) {
         switch (mState) {
             case STATE_UNINITIALIZED:
             case STATE_NOT_SUPPORTED:
@@ -1197,14 +1219,18 @@
             case STATE_ENABLED_AVAILABLE:
                 if (mSpat == null) {
                     // try to recover by resetting the native spatializer state
-                    Log.e(TAG, "checkSpatForHeadTracking(): "
-                            + "native spatializer should not be null in state: " + mState);
+                    Log.e(TAG, "checkSpatializer(): called from " + funcName
+                            + "(), native spatializer should not be null in state: " + mState);
                     postReset();
                     return false;
                 }
                 break;
         }
-        return mIsHeadTrackingSupported;
+        return true;
+    }
+
+    private boolean checkSpatializerForHeadTracking(String funcName) {
+        return checkSpatializer(funcName) && mIsHeadTrackingSupported;
     }
 
     private void dispatchActualHeadTrackingMode(int newMode) {
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
index 8b80674..daca00b 100644
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -6,5 +6,24 @@
         {
             "name": "CtsBiometricsHostTestCases"
         }
-    ]
-}
\ No newline at end of file
+    ],
+    "ironwood-postsubmit": [
+     {
+      "name": "BiometricsE2eTests",
+      "options": [
+        {
+            "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+            "include-filter": "android.platform.test.scenario.biometrics"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        }
+      ]
+    }
+   ]
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index dc00ffc..01ffc7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -314,7 +314,8 @@
             final FingerprintSensorPropertiesInternal sensorProps =
                     provider.second.getSensorProperties(options.getSensorId());
             if (!isKeyguard && !Utils.isSettings(getContext(), opPackageName)
-                    && sensorProps != null && sensorProps.isAnyUdfpsType()) {
+                    && sensorProps != null && (sensorProps.isAnyUdfpsType()
+                    || sensorProps.isAnySidefpsType())) {
                 try {
                     return authenticateWithPrompt(operationId, sensorProps, callingUid,
                             callingUserId, receiver, opPackageName,
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 4e3de3c..e8af840 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -243,11 +243,13 @@
         public List<CameraStreamStats> mStreamStats;
         public String mUserTag;
         public int mVideoStabilizationMode;
+        public final long mLogId;
 
         private long mDurationOrStartTimeMs;  // Either start time, or duration once completed
 
         CameraUsageEvent(String cameraId, int facing, String clientName, int apiLevel,
-                boolean isNdk, int action, int latencyMs, int operatingMode, boolean deviceError) {
+                boolean isNdk, int action, int latencyMs, int operatingMode, boolean deviceError,
+                long logId) {
             mCameraId = cameraId;
             mCameraFacing = facing;
             mClientName = clientName;
@@ -259,6 +261,7 @@
             mLatencyMs = latencyMs;
             mOperatingMode = operatingMode;
             mDeviceError = deviceError;
+            mLogId = logId;
         }
 
         public void markCompleted(int internalReconfigure, long requestCount,
@@ -840,7 +843,8 @@
                         + ", deviceError " + e.mDeviceError
                         + ", streamCount is " + streamCount
                         + ", userTag is " + e.mUserTag
-                        + ", videoStabilizationMode " + e.mVideoStabilizationMode);
+                        + ", videoStabilizationMode " + e.mVideoStabilizationMode
+                        + ", logId " + e.mLogId);
             }
             // Convert from CameraStreamStats to CameraStreamProto
             CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS];
@@ -900,7 +904,7 @@
                     MessageNano.toByteArray(streamProtos[2]),
                     MessageNano.toByteArray(streamProtos[3]),
                     MessageNano.toByteArray(streamProtos[4]),
-                    e.mUserTag, e.mVideoStabilizationMode);
+                    e.mUserTag, e.mVideoStabilizationMode, e.mLogId);
         }
     }
 
@@ -1089,6 +1093,7 @@
         List<CameraStreamStats> streamStats = cameraState.getStreamStats();
         String userTag = cameraState.getUserTag();
         int videoStabilizationMode = cameraState.getVideoStabilizationMode();
+        long logId = cameraState.getLogId();
         synchronized(mLock) {
             // Update active camera list and notify NFC if necessary
             boolean wasEmpty = mActiveCameraUsage.isEmpty();
@@ -1110,7 +1115,7 @@
                     CameraUsageEvent openEvent = new CameraUsageEvent(
                             cameraId, facing, clientName, apiLevel, isNdk,
                             FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__OPEN,
-                            latencyMs, sessionType, deviceError);
+                            latencyMs, sessionType, deviceError, logId);
                     mCameraUsageHistory.add(openEvent);
                     break;
                 case CameraSessionStats.CAMERA_STATE_ACTIVE:
@@ -1137,7 +1142,7 @@
                     CameraUsageEvent newEvent = new CameraUsageEvent(
                             cameraId, facing, clientName, apiLevel, isNdk,
                             FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__SESSION,
-                            latencyMs, sessionType, deviceError);
+                            latencyMs, sessionType, deviceError, logId);
                     CameraUsageEvent oldEvent = mActiveCameraUsage.put(cameraId, newEvent);
                     if (oldEvent != null) {
                         Slog.w(TAG, "Camera " + cameraId + " was already marked as active");
@@ -1181,7 +1186,7 @@
                         CameraUsageEvent closeEvent = new CameraUsageEvent(
                                 cameraId, facing, clientName, apiLevel, isNdk,
                                 FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__CLOSE,
-                                latencyMs, sessionType, deviceError);
+                                latencyMs, sessionType, deviceError, logId);
                         mCameraUsageHistory.add(closeEvent);
                     }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index a589313..00af224 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -76,7 +76,13 @@
      * This flag indicates that the corresponding state should be disabled when the device is
      * overheating and reaching the critical status.
      */
-    public static final int FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
+    public static final int FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
+
+    /**
+     * This flag indicates that the corresponding state should be disabled when power save mode
+     * is enabled.
+     */
+    public static final int FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = 1 << 5;
 
     /** @hide */
     @IntDef(prefix = {"FLAG_"}, flag = true, value = {
@@ -84,7 +90,8 @@
             FLAG_APP_INACCESSIBLE,
             FLAG_EMULATED_ONLY,
             FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
-            FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL
+            FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+            FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeviceStateFlags {}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 43ee5e2..9645690 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -25,6 +25,7 @@
 import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
+import static com.android.server.devicestate.OverrideRequestController.FLAG_POWER_SAVE_ENABLED;
 import static com.android.server.devicestate.OverrideRequestController.FLAG_THERMAL_CRITICAL;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
@@ -609,7 +610,8 @@
 
     @GuardedBy("mLock")
     private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
-            @OverrideRequestController.RequestStatus int status, int flags) {
+            @OverrideRequestController.RequestStatus int status,
+            @OverrideRequestController.StatusChangedFlag int flags) {
         if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
             switch (status) {
                 case STATUS_ACTIVE:
@@ -641,6 +643,10 @@
                             mDeviceStateNotificationController
                                     .showThermalCriticalNotificationIfNeeded(
                                             request.getRequestedState());
+                        } else if ((flags & FLAG_POWER_SAVE_ENABLED) == FLAG_POWER_SAVE_ENABLED) {
+                            mDeviceStateNotificationController
+                                    .showPowerSaveNotificationIfNeeded(
+                                            request.getRequestedState());
                         }
                     }
                     break;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
index 9008740..ab261ac 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.devicestate;
 
+import static android.provider.Settings.ACTION_BATTERY_SAVER_SETTINGS;
+
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -101,10 +103,16 @@
         }
         String requesterApplicationLabel = getApplicationLabel(requestingAppUid);
         if (requesterApplicationLabel != null) {
+            final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
+                    .setPackage(mContext.getPackageName());
+            final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                    mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
             showNotification(
                     info.name, info.activeNotificationTitle,
                     String.format(info.activeNotificationContent, requesterApplicationLabel),
-                    true /* ongoing */, R.drawable.ic_dual_screen
+                    true /* ongoing */, R.drawable.ic_dual_screen,
+                    pendingIntent,
+                    mContext.getString(R.string.device_state_notification_turn_off_button)
             );
         } else {
             Slog.e(TAG, "Cannot determine the requesting app name when showing state active "
@@ -126,7 +134,33 @@
         showNotification(
                 info.name, info.thermalCriticalNotificationTitle,
                 info.thermalCriticalNotificationContent, false /* ongoing */,
-                R.drawable.ic_thermostat
+                R.drawable.ic_thermostat,
+                null /* pendingIntent */,
+                null /* actionText */
+        );
+    }
+
+    /**
+     * Displays the notification indicating that the device state is canceled due to power
+     * save mode being enabled. Does nothing if the state does not have a power save mode
+     * notification.
+     *
+     * @param state the identifier of the device state being canceled.
+     */
+    void showPowerSaveNotificationIfNeeded(int state) {
+        NotificationInfo info = mNotificationInfos.get(state);
+        if (info == null || !info.hasPowerSaveModeNotification()) {
+            return;
+        }
+        final Intent intent = new Intent(ACTION_BATTERY_SAVER_SETTINGS);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(
+                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+        showNotification(
+                info.name, info.powerSaveModeNotificationTitle,
+                info.powerSaveModeNotificationContent, false /* ongoing */,
+                R.drawable.ic_thermostat,
+                pendingIntent,
+                mContext.getString(R.string.device_state_notification_settings_button)
         );
     }
 
@@ -161,7 +195,8 @@
      */
     private void showNotification(
             @NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing,
-            @DrawableRes int iconRes) {
+            @DrawableRes int iconRes,
+            @Nullable PendingIntent pendingIntent, @Nullable String actionText) {
         final NotificationChannel channel = new NotificationChannel(
                 CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
         final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
@@ -173,14 +208,10 @@
                 .setOngoing(ongoing)
                 .setCategory(Notification.CATEGORY_SYSTEM);
 
-        if (ongoing) {
-            final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
-                    .setPackage(mContext.getPackageName());
-            final PendingIntent pendingIntent = PendingIntent.getBroadcast(
-                    mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+        if (pendingIntent != null && actionText != null) {
             final Notification.Action action = new Notification.Action.Builder(
                     null /* icon */,
-                    mContext.getString(R.string.device_state_notification_turn_off_button),
+                    actionText,
                     pendingIntent)
                     .build();
             builder.addAction(action);
@@ -215,12 +246,21 @@
         final String[] thermalCriticalNotificationContents =
                 context.getResources().getStringArray(
                         R.array.device_state_notification_thermal_contents);
+        final String[] powerSaveModeNotificationTitles =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_power_save_titles);
+        final String[] powerSaveModeNotificationContents =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_power_save_contents);
+
 
         if (stateIdentifiers.length != names.length
                 || stateIdentifiers.length != activeNotificationTitles.length
                 || stateIdentifiers.length != activeNotificationContents.length
                 || stateIdentifiers.length != thermalCriticalNotificationTitles.length
                 || stateIdentifiers.length != thermalCriticalNotificationContents.length
+                || stateIdentifiers.length != powerSaveModeNotificationTitles.length
+                || stateIdentifiers.length != powerSaveModeNotificationContents.length
         ) {
             throw new IllegalStateException(
                     "The length of state identifiers and notification texts must match!");
@@ -237,7 +277,9 @@
                     new NotificationInfo(
                             names[i], activeNotificationTitles[i], activeNotificationContents[i],
                             thermalCriticalNotificationTitles[i],
-                            thermalCriticalNotificationContents[i])
+                            thermalCriticalNotificationContents[i],
+                            powerSaveModeNotificationTitles[i],
+                            powerSaveModeNotificationContents[i])
             );
         }
 
@@ -272,16 +314,21 @@
         public final String activeNotificationContent;
         public final String thermalCriticalNotificationTitle;
         public final String thermalCriticalNotificationContent;
+        public final String powerSaveModeNotificationTitle;
+        public final String powerSaveModeNotificationContent;
 
         NotificationInfo(String name, String activeNotificationTitle,
                 String activeNotificationContent, String thermalCriticalNotificationTitle,
-                String thermalCriticalNotificationContent) {
+                String thermalCriticalNotificationContent, String powerSaveModeNotificationTitle,
+                String powerSaveModeNotificationContent) {
 
             this.name = name;
             this.activeNotificationTitle = activeNotificationTitle;
             this.activeNotificationContent = activeNotificationContent;
             this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle;
             this.thermalCriticalNotificationContent = thermalCriticalNotificationContent;
+            this.powerSaveModeNotificationTitle = powerSaveModeNotificationTitle;
+            this.powerSaveModeNotificationContent = powerSaveModeNotificationContent;
         }
 
         boolean hasActiveNotification() {
@@ -292,5 +339,10 @@
             return thermalCriticalNotificationTitle != null
                     && thermalCriticalNotificationTitle.length() > 0;
         }
+
+        boolean hasPowerSaveModeNotification() {
+            return powerSaveModeNotificationTitle != null
+                    && powerSaveModeNotificationTitle.length() > 0;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index fecc13f..af33de0 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -52,11 +52,24 @@
      */
     int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3;
 
+    /**
+     * Indicating that the supported device states have changed because power save mode was enabled.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED = 4;
+
+    /**
+     * Indicating that the supported device states have changed because power save mode was
+     * disabled.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED = 5;
+
     @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = {
             SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT,
             SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED,
             SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL,
-            SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+            SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL,
+            SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED,
+            SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface SupportedStatesUpdatedReason {}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 2ed4765..46f0bc0 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -64,6 +64,18 @@
      */
     static final int FLAG_THERMAL_CRITICAL = 1 << 0;
 
+    /**
+     * A flag indicating that the status change was triggered by power save mode.
+     */
+    static final int FLAG_POWER_SAVE_ENABLED = 1 << 1;
+
+    @IntDef(flag = true, prefix = {"FLAG_"}, value = {
+            FLAG_THERMAL_CRITICAL,
+            FLAG_POWER_SAVE_ENABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface StatusChangedFlag {}
+
     static String statusToString(@RequestStatus int status) {
         switch (status) {
             case STATUS_ACTIVE:
@@ -228,13 +240,18 @@
             @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
         boolean isThermalCritical =
                 reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
+        boolean isPowerSaveEnabled =
+                reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED;
+        @StatusChangedFlag int flags = 0;
+        flags |= isThermalCritical ? FLAG_THERMAL_CRITICAL : 0;
+        flags |= isPowerSaveEnabled ? FLAG_POWER_SAVE_ENABLED : 0;
         if (mBaseStateRequest != null && !contains(newSupportedStates,
                 mBaseStateRequest.getRequestedState())) {
-            cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
+            cancelCurrentBaseStateRequestLocked(flags);
         }
 
         if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
-            cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
+            cancelCurrentRequestLocked(flags);
         }
     }
 
@@ -255,7 +272,8 @@
         cancelRequestLocked(requestToCancel, 0 /* flags */);
     }
 
-    private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) {
+    private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel,
+            @StatusChangedFlag int flags) {
         mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags);
     }
 
@@ -267,7 +285,7 @@
         cancelCurrentRequestLocked(0 /* flags */);
     }
 
-    private void cancelCurrentRequestLocked(int flags) {
+    private void cancelCurrentRequestLocked(@StatusChangedFlag int flags) {
         if (mRequest == null) {
             Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
             return;
@@ -285,7 +303,7 @@
         cancelCurrentBaseStateRequestLocked(0 /* flags */);
     }
 
-    private void cancelCurrentBaseStateRequestLocked(int flags) {
+    private void cancelCurrentBaseStateRequestLocked(@StatusChangedFlag int flags) {
         if (mBaseStateRequest == null) {
             Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
             return;
@@ -312,6 +330,6 @@
          * cancelled request.
          */
         void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus,
-                int flags);
+                @StatusChangedFlag int flags);
     }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index f873a1b..4d4a87e 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -65,6 +65,7 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.XmlUtils;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import libcore.io.Streams;
 
@@ -1226,9 +1227,15 @@
                 mContext.getSystemService(UserManager.class));
         InputMethodManager inputMethodManager = Objects.requireNonNull(
                 mContext.getSystemService(InputMethodManager.class));
+        // Need to use InputMethodManagerInternal to call getEnabledInputMethodListAsUser()
+        // instead of using InputMethodManager which uses enforceCallingPermissions() that
+        // breaks when we are calling the method for work profile user ID since it doesn't check
+        // self permissions.
+        InputMethodManagerInternal inputMethodManagerInternal = InputMethodManagerInternal.get();
         for (UserHandle userHandle : userManager.getUserHandles(true /* excludeDying */)) {
             int userId = userHandle.getIdentifier();
-            for (InputMethodInfo imeInfo : inputMethodManager.getEnabledInputMethodListAsUser(
+            for (InputMethodInfo imeInfo :
+                    inputMethodManagerInternal.getEnabledInputMethodListAsUser(
                     userId)) {
                 for (InputMethodSubtype imeSubtype :
                         inputMethodManager.getEnabledInputMethodSubtypeList(
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 9f7ff31..0ae1e80 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -136,17 +136,16 @@
                 mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
                 break;
             case STATE_HIDE_IME:
-                if (mService.mCurFocusedWindowClient != null) {
+                if (mService.hasAttachedClient()) {
                     ImeTracker.forLogging().onProgress(statsToken,
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                     // IMMS only knows of focused window, not the actual IME target.
                     // e.g. it isn't aware of any window that has both
                     // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
-                    // Send it to window manager to hide IME from IME target window.
-                    // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
-                    // actual IME target.
+                    // Send it to window manager to hide IME from the actual IME control target
+                    // of the target display.
                     mWindowManagerInternal.hideIme(windowToken,
-                            mService.mCurFocusedWindowClient.mSelfReportedDisplayId, statsToken);
+                            mService.getDisplayIdToShowImeLocked(), statsToken);
                 } else {
                     ImeTracker.forLogging().onFailed(statsToken,
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index e32bf1b..b336b95 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2339,6 +2339,19 @@
         }
     }
 
+    /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
+    @GuardedBy("ImfLock.class")
+    boolean hasAttachedClient() {
+        return mCurClient != null;
+    }
+
+    @VisibleForTesting
+    void setAttachedClientForTesting(@NonNull ClientState cs) {
+        synchronized (ImfLock.class) {
+            mCurClient = cs;
+        }
+    }
+
     @GuardedBy("ImfLock.class")
     void clearInputShownLocked() {
         mVisibilityStateComputer.setInputShown(false);
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 77cd673..a081dff 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -92,6 +92,8 @@
     // Represents an HAL interface version. Instances of this class are created in the JNI layer
     // and returned through native methods.
     static class HalInterfaceVersion {
+        // mMajor being this value denotes AIDL HAL. In this case, mMinor denotes the AIDL version.
+        static final int AIDL_INTERFACE = 3;
         final int mMajor;
         final int mMinor;
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 6c4c829..041f11d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -21,6 +21,7 @@
 import static com.android.server.location.gnss.GnssManagerService.D;
 import static com.android.server.location.gnss.GnssManagerService.TAG;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.location.GnssMeasurementRequest;
@@ -31,6 +32,7 @@
 import android.stats.location.LocationStatsEnums;
 import android.util.Log;
 
+import com.android.server.location.gnss.GnssConfiguration.HalInterfaceVersion;
 import com.android.server.location.gnss.hal.GnssNative;
 import com.android.server.location.injector.AppOpsHelper;
 import com.android.server.location.injector.Injector;
@@ -115,16 +117,6 @@
         if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
             return true;
         }
-        // The HAL doc does not specify if consecutive start() calls will be allowed.
-        // Some vendors may ignore the 2nd start() call if stop() is not called.
-        // Thus, here we always call stop() before calling start() to avoid being ignored.
-        if (mGnssNative.stopMeasurementCollection()) {
-            if (D) {
-                Log.d(TAG, "stopping gnss measurements");
-            }
-        } else {
-            Log.e(TAG, "error stopping gnss measurements");
-        }
         if (mGnssNative.startMeasurementCollection(request.isFullTracking(),
                 request.isCorrelationVectorOutputsEnabled(),
                 request.getIntervalMillis())) {
@@ -139,6 +131,28 @@
     }
 
     @Override
+    protected boolean reregisterWithService(GnssMeasurementRequest old,
+            GnssMeasurementRequest request,
+            @NonNull Collection<GnssListenerRegistration> registrations) {
+        if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+            unregisterWithService();
+            return true;
+        }
+        HalInterfaceVersion halInterfaceVersion =
+                mGnssNative.getConfiguration().getHalInterfaceVersion();
+        boolean aidlV3Plus = halInterfaceVersion.mMajor == HalInterfaceVersion.AIDL_INTERFACE
+                && halInterfaceVersion.mMinor >= 3;
+        if (!aidlV3Plus) {
+            // The HAL doc does not specify if consecutive start() calls will be allowed.
+            // Some vendors may ignore the 2nd start() call if stop() is not called.
+            // Thus, here we always call stop() before calling start() to avoid being ignored.
+            // AIDL v3+ is free from this issue.
+            unregisterWithService();
+        }
+        return registerWithService(request, registrations);
+    }
+
+    @Override
     protected void unregisterWithService() {
         if (mGnssNative.stopMeasurementCollection()) {
             if (D) {
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index c232b36..9748aba 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -41,6 +41,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -499,7 +500,7 @@
     String getInstallerPackageName(@NonNull String packageName, @UserIdInt int userId);
 
     @Nullable
-    InstallSourceInfo getInstallSourceInfo(@NonNull String packageName);
+    InstallSourceInfo getInstallSourceInfo(@NonNull String packageName, @UserIdInt int userId);
 
     @PackageManager.EnabledState
     int getApplicationEnabledSetting(@NonNull String packageName, @UserIdInt int userId);
@@ -519,14 +520,15 @@
      * returns false.
      */
     boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo,
-            @UserIdInt int userId);
+            @NonNull UserHandle userHandle);
 
     /**
      * @return true if the runtime app user enabled state and the install-time app manifest enabled
      * state are both effectively enabled for the given app. Or if the app cannot be found,
      * returns false.
      */
-    boolean isApplicationEffectivelyEnabled(@NonNull String packageName, @UserIdInt int userId);
+    boolean isApplicationEffectivelyEnabled(@NonNull String packageName,
+            @NonNull UserHandle userHandle);
 
     @Nullable
     KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 5984360..acd4a96 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4982,9 +4982,11 @@
 
     @Override
     @Nullable
-    public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) {
+    public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName,
+            @UserIdInt int userId) {
         final int callingUid = Binder.getCallingUid();
-        final int userId = UserHandle.getUserId(callingUid);
+        enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
+                false /* checkShell */, "getInstallSourceInfo");
 
         String installerPackageName;
         String initiatingPackageName;
@@ -5129,9 +5131,10 @@
 
     @Override
     public boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo,
-            @UserIdInt int userId) {
+            @NonNull UserHandle userHandle) {
         try {
             String packageName = componentInfo.packageName;
+            int userId = userHandle.getIdentifier();
             int appEnabledSetting =
                     mSettings.getApplicationEnabledSetting(packageName, userId);
             if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) {
@@ -5154,9 +5157,10 @@
 
     @Override
     public boolean isApplicationEffectivelyEnabled(@NonNull String packageName,
-            @UserIdInt int userId) {
+            @NonNull UserHandle userHandle) {
         try {
-            int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName, userId);
+            int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName,
+                    userHandle.getIdentifier());
             if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) {
                 final AndroidPackage pkg = getPackage(packageName);
                 if (pkg == null) {
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index d39cac0..c29e4d7 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -463,8 +463,9 @@
     @Override
     @Nullable
     @Deprecated
-    public final InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) {
-        return snapshot().getInstallSourceInfo(packageName);
+    public final InstallSourceInfo getInstallSourceInfo(@NonNull String packageName,
+            @UserIdInt int userId) {
+        return snapshot().getInstallSourceInfo(packageName, userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index fa535c3..03e0d36 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -925,7 +925,7 @@
         final int targetPackageUid = snapshot.getPackageUid(packageName, 0, userId);
         final boolean isUpdate = targetPackageUid != -1 || isApexSession();
         final InstallSourceInfo existingInstallSourceInfo = isUpdate
-                ? snapshot.getInstallSourceInfo(packageName)
+                ? snapshot.getInstallSourceInfo(packageName, userId)
                 : null;
         final String existingInstallerPackageName = existingInstallSourceInfo != null
                 ? existingInstallSourceInfo.getInstallingPackageName()
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4d74567..6bc8760 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1328,7 +1328,8 @@
             throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
         }
 
-        final InstallSourceInfo installSourceInfo = snapshot.getInstallSourceInfo(packageName);
+        final InstallSourceInfo installSourceInfo = snapshot.getInstallSourceInfo(packageName,
+                userId);
         final String installerPackageName;
         if (installSourceInfo != null) {
             if (!TextUtils.isEmpty(installSourceInfo.getInitiatingPackageName())) {
@@ -2569,7 +2570,7 @@
 
             if (best == null || cur.priority > best.priority) {
                 if (computer.isComponentEffectivelyEnabled(cur.getComponentInfo(),
-                        UserHandle.USER_SYSTEM)) {
+                        UserHandle.SYSTEM)) {
                     best = cur;
                 } else {
                     Slog.w(TAG, "Domain verification agent found but not enabled");
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 5b967ec..f340f93 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -26,7 +26,6 @@
 import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
 import static android.os.PowerWhitelistManager.REASON_PACKAGE_VERIFIER;
 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
-import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
@@ -408,7 +407,7 @@
         final int numRequiredVerifierPackages = requiredVerifierPackages.size();
         for (int i = numRequiredVerifierPackages - 1; i >= 0; i--) {
             if (!snapshot.isApplicationEffectivelyEnabled(requiredVerifierPackages.get(i),
-                    SYSTEM_UID)) {
+                    verifierUser)) {
                 Slog.w(TAG,
                         "Required verifier: " + requiredVerifierPackages.get(i) + " is disabled");
                 requiredVerifierPackages.remove(i);
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 8d7f782..3644054 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -21,7 +21,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -101,8 +104,10 @@
     private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
     private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
             "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
-    private static final String FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL =
-            "FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL";
+    private static final String FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL =
+            "FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL";
+    private static final String FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE =
+            "FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE";
 
     /** Interface that allows reading the device state configuration. */
     interface ReadableConfig {
@@ -162,9 +167,12 @@
                                 case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
                                     flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
                                     break;
-                                case FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL:
-                                    flags |= DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL;
+                                case FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL:
+                                    flags |= DeviceState
+                                            .FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
                                     break;
+                                case FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE:
+                                    flags |= DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
                                 default:
                                     Slog.w(TAG, "Parsed unknown flag with name: "
                                             + configFlagString);
@@ -210,6 +218,9 @@
     @GuardedBy("mLock")
     private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE;
 
+    @GuardedBy("mLock")
+    private boolean mPowerSaveModeEnabled;
+
     private DeviceStateProviderImpl(@NonNull Context context,
             @NonNull List<DeviceState> deviceStates,
             @NonNull List<Conditions> stateConditions) {
@@ -224,14 +235,32 @@
 
         setStateConditions(deviceStates, stateConditions);
 
-        // If any of the device states are thermal sensitive, i.e. it should be disabled when the
-        // device is overheating, then we will update the list of supported states when thermal
-        // status changes.
-        if (hasThermalSensitiveState(deviceStates)) {
-            PowerManager powerManager = context.getSystemService(PowerManager.class);
-            if (powerManager != null) {
+        PowerManager powerManager = context.getSystemService(PowerManager.class);
+        if (powerManager != null) {
+            // If any of the device states are thermal sensitive, i.e. it should be disabled when
+            // the device is overheating, then we will update the list of supported states when
+            // thermal status changes.
+            if (hasThermalSensitiveState(deviceStates)) {
                 powerManager.addThermalStatusListener(this);
             }
+
+            // If any of the device states are power sensitive, i.e. it should be disabled when
+            // power save mode is enabled, then we will update the list of supported states when
+            // power save mode is toggled.
+            if (hasPowerSaveSensitiveState(deviceStates)) {
+                IntentFilter filter = new IntentFilter(
+                        PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
+                BroadcastReceiver receiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL.equals(
+                                intent.getAction())) {
+                            onPowerSaveModeChanged(powerManager.isPowerSaveMode());
+                        }
+                    }
+                };
+                mContext.registerReceiver(receiver, filter);
+            }
         }
     }
 
@@ -382,7 +411,11 @@
             for (DeviceState deviceState : mOrderedStates) {
                 if (isThermalStatusCriticalOrAbove(mThermalStatus)
                         && deviceState.hasFlag(
-                                DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+                                DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+                    continue;
+                }
+                if (mPowerSaveModeEnabled && deviceState.hasFlag(
+                        DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
                     continue;
                 }
                 supportedStates.add(deviceState);
@@ -674,6 +707,18 @@
         }
     }
 
+    @VisibleForTesting
+    void onPowerSaveModeChanged(boolean isPowerSaveModeEnabled) {
+        synchronized (mLock) {
+            if (mPowerSaveModeEnabled != isPowerSaveModeEnabled) {
+                mPowerSaveModeEnabled = isPowerSaveModeEnabled;
+                notifySupportedStatesChanged(
+                        isPowerSaveModeEnabled ? SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED
+                                : SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED);
+            }
+        }
+    }
+
     @Override
     public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) {
         int previousThermalStatus;
@@ -709,7 +754,16 @@
 
     private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) {
         for (DeviceState state : deviceStates) {
-            if (state.hasFlag(DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+            if (state.hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasPowerSaveSensitiveState(List<DeviceState> deviceStates) {
+        for (int i = 0; i < deviceStates.size(); i++) {
+            if (deviceStates.get(i).hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 324a0ad..e21c156 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4357,17 +4357,6 @@
     @Override
     void addWindow(WindowState w) {
         super.addWindow(w);
-
-        boolean gotReplacementWindow = false;
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState candidate = mChildren.get(i);
-            gotReplacementWindow |= candidate.setReplacementWindowIfNeeded(w);
-        }
-
-        // if we got a replacement window, reset the timeout to give drawing more time
-        if (gotReplacementWindow) {
-            mWmService.scheduleWindowReplacementTimeouts(this);
-        }
         checkKeyguardFlagsChanged();
     }
 
@@ -4382,12 +4371,6 @@
         updateLetterboxSurface(child);
     }
 
-    void onWindowReplacementTimeout() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            (mChildren.get(i)).onWindowReplacementTimeout();
-        }
-    }
-
     void setAppLayoutChanges(int changes, String reason) {
         if (!mChildren.isEmpty()) {
             final DisplayContent dc = getDisplayContent();
@@ -4398,15 +4381,6 @@
         }
     }
 
-    void removeReplacedWindowIfNeeded(WindowState replacement) {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState win = mChildren.get(i);
-            if (win.removeReplacedWindowIfNeeded(replacement)) {
-                return;
-            }
-        }
-    }
-
     private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) {
         final WindowState tStartingWindow = fromActivity.mStartingWindow;
         if (tStartingWindow != null && fromActivity.mStartingSurface != null) {
@@ -5633,10 +5607,7 @@
         // * activity is transitioning visibility state
         // * or the activity was marked as hidden and is exiting before we had a chance to play the
         // transition animation
-        // * or this is an opening app and windows are being replaced (e.g. freeform window to
-        //   normal window).
-        return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting)
-                || (visible && forAllWindows(WindowState::waitingForReplacement, true));
+        return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting);
     }
 
     /**
@@ -7380,35 +7351,6 @@
         }
     }
 
-    void setWillReplaceWindows(boolean animate) {
-        ProtoLog.d(WM_DEBUG_ADD_REMOVE,
-                "Marking app token %s with replacing windows.", this);
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState w = mChildren.get(i);
-            w.setWillReplaceWindow(animate);
-        }
-    }
-
-    void setWillReplaceChildWindows() {
-        ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Marking app token %s"
-                + " with replacing child windows.", this);
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState w = mChildren.get(i);
-            w.setWillReplaceChildWindows();
-        }
-    }
-
-    void clearWillReplaceWindows() {
-        ProtoLog.d(WM_DEBUG_ADD_REMOVE,
-                "Resetting app token %s of replacing window marks.", this);
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState w = mChildren.get(i);
-            w.clearWillReplaceWindow();
-        }
-    }
-
     void requestUpdateWallpaperIfNeeded() {
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final WindowState w = mChildren.get(i);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 0b28ba2..bc9efc8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -377,15 +377,10 @@
             mDragWindowHandle.ownerUid = MY_UID;
             mDragWindowHandle.scaleFactor = 1.0f;
 
-            // InputConfig.PREVENT_SPLITTING: To keep the default behavior of this window to be
-            // focusable, which allows the system to consume keys when dragging is active. This can
-            // also be used to modify the drag state on key press. For example, cancel drag on
-            // escape key.
             // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing
             // touches to pass through to windows underneath. This allows user to interact with the
             // UI to navigate while dragging.
-            mDragWindowHandle.inputConfig =
-                    InputConfig.PREVENT_SPLITTING | InputConfig.TRUSTED_OVERLAY;
+            mDragWindowHandle.inputConfig = InputConfig.TRUSTED_OVERLAY;
 
             // The drag window cannot receive new touches.
             mDragWindowHandle.touchableRegion.setEmpty();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index b2a4df1..fda2125 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -44,7 +44,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -432,13 +431,6 @@
         }
     };
 
-    private static final Consumer<WindowState> sRemoveReplacedWindowsConsumer = w -> {
-        final ActivityRecord activity = w.mActivityRecord;
-        if (activity != null) {
-            activity.removeReplacedWindowIfNeeded(w);
-        }
-    };
-
     RootWindowContainer(WindowManagerService service) {
         super(service);
         mHandler = new MyHandler(service.mH.getLooper());
@@ -662,17 +654,6 @@
         forAllWindows(mCloseSystemDialogsConsumer, false /* traverseTopToBottom */);
     }
 
-    void removeReplacedWindows() {
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION removeReplacedWindows");
-        mWmService.openSurfaceTransaction();
-        try {
-            forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
-        } finally {
-            mWmService.closeSurfaceTransaction("removeReplacedWindows");
-            ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION removeReplacedWindows");
-        }
-    }
-
     boolean hasPendingLayoutChanges(WindowAnimator animator) {
         boolean hasChanges = false;
 
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index ce9bff8..78ee6f9 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -232,11 +232,6 @@
     }
 
     @Override
-    public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
-        mService.setWillReplaceWindows(appToken, childrenOnly);
-    }
-
-    @Override
     public boolean cancelDraw(IWindow window) {
         return mService.cancelDraw(this, window);
     }
@@ -862,7 +857,7 @@
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface,
             IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
-            IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
+            int inputFeatures, IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
             InputChannel outInputChannel) {
         if (hostInputToken == null && !mCanAddInternalSystemWindow) {
             // Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
@@ -874,7 +869,7 @@
         try {
             mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
                     flags, mCanAddInternalSystemWindow ? privateFlags : 0,
-                    type, windowToken, focusGrantToken, inputHandleName,
+                    type, inputFeatures, windowToken, focusGrantToken, inputHandleName,
                     outInputChannel);
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -883,11 +878,11 @@
 
     @Override
     public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface,
-            int flags, int privateFlags, Region region) {
+            int flags, int privateFlags, int inputFeatures, Region region) {
         final long identity = Binder.clearCallingIdentity();
         try {
             mService.updateInputChannel(channelToken, displayId, surface, flags,
-                    mCanAddInternalSystemWindow ? privateFlags : 0, region);
+                    mCanAddInternalSystemWindow ? privateFlags : 0, inputFeatures, region);
         } 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 67ca844..6882e4c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -866,23 +866,8 @@
             return false;
         }
 
-        final int toRootTaskWindowingMode = toRootTask.getWindowingMode();
         final ActivityRecord topActivity = getTopNonFinishingActivity();
 
-        final boolean mightReplaceWindow = topActivity != null
-                && replaceWindowsOnTaskMove(getWindowingMode(), toRootTaskWindowingMode);
-        if (mightReplaceWindow) {
-            // We are about to relaunch the activity because its configuration changed due to
-            // being maximized, i.e. size change. The activity will first remove the old window
-            // and then add a new one. This call will tell window manager about this, so it can
-            // preserve the old window until the new one is drawn. This prevents having a gap
-            // between the removal and addition, in which no window is visible. We also want the
-            // entrance of the new window to be properly animated.
-            // Note here we always set the replacing window first, as the flags might be needed
-            // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
-            windowManager.setWillReplaceWindow(topActivity.token, animate);
-        }
-
         mAtmService.deferWindowLayout();
         boolean kept = true;
         try {
@@ -926,17 +911,10 @@
             mAtmService.continueWindowLayout();
         }
 
-        if (mightReplaceWindow) {
-            // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
-            // window), we need to clear the replace window settings. Otherwise, we schedule a
-            // timeout to remove the old window if the replacing window is not coming in time.
-            windowManager.scheduleClearWillReplaceWindows(topActivity.token, !kept);
-        }
-
         if (!deferResume) {
             // The task might have already been running and its visibility needs to be synchronized
             // with the visibility of the root task / windows.
-            root.ensureActivitiesVisible(null, 0, !mightReplaceWindow);
+            root.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
             root.resumeFocusedTasksTopActivities();
         }
 
@@ -947,17 +925,6 @@
         return (preferredRootTask == toRootTask);
     }
 
-    /**
-     * @return {@code true} if the windows of tasks being moved to the target root task from the
-     * source root task should be replaced, meaning that window manager will keep the old window
-     * around until the new is ready.
-     */
-    private static boolean replaceWindowsOnTaskMove(
-            int sourceWindowingMode, int targetWindowingMode) {
-        return sourceWindowingMode == WINDOWING_MODE_FREEFORM
-                || targetWindowingMode == WINDOWING_MODE_FREEFORM;
-    }
-
     void touchActiveTime() {
         lastActiveTime = SystemClock.elapsedRealtime();
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 97f48b7..370d304 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1012,6 +1012,7 @@
             dc.removeImeSurfaceImmediately();
             dc.handleCompleteDeferredRemoval();
         }
+        validateVisibility();
 
         mState = STATE_FINISHED;
         mController.mTransitionTracer.logState(this);
@@ -1920,6 +1921,7 @@
                 change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken());
             }
             change.setMode(info.getTransitMode(target));
+            info.mReadyMode = change.getMode();
             change.setStartAbsBounds(info.mAbsoluteBounds);
             change.setFlags(info.getChangeFlags(target));
             change.setDisplayId(info.mDisplayId, getDisplayId(target));
@@ -2177,6 +2179,26 @@
         return mainWin.getAttrs().rotationAnimation;
     }
 
+    private void validateVisibility() {
+        for (int i = mTargets.size() - 1; i >= 0; --i) {
+            if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) {
+                return;
+            }
+        }
+        // All modes are CLOSE. The surfaces may be hidden by the animation unexpectedly.
+        // If the window container should be visible, then recover it.
+        mController.mStateValidators.add(() -> {
+            for (int i = mTargets.size() - 1; i >= 0; --i) {
+                final ChangeInfo change = mTargets.get(i);
+                if (!change.mContainer.isVisibleRequested()) continue;
+                Slog.e(TAG, "Force show for visible " + change.mContainer
+                        + " which may be hidden by transition unexpectedly");
+                change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl);
+                change.mContainer.scheduleAnimation();
+            }
+        });
+    }
+
     /** Applies the new configuration for the changed displays. */
     void applyDisplayChangeIfNeeded() {
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
@@ -2262,6 +2284,10 @@
         SurfaceControl mSnapshot;
         float mSnapshotLuma;
 
+        /** The mode which is set when the transition is ready. */
+        @TransitionInfo.TransitionMode
+        int mReadyMode;
+
         ChangeInfo(@NonNull WindowContainer origState) {
             mContainer = origState;
             mVisible = origState.isVisibleRequested();
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 2b848d5..0b9ceea 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -163,14 +163,6 @@
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
                 + " mDrawState=" + w.mWinAnimator.mDrawState);
 
-        if (w.mWillReplaceWindow && mWallpaperTarget == null
-                && !mFindResults.useTopWallpaperAsTarget) {
-            // When we are replacing a window and there was wallpaper before replacement, we want to
-            // keep the window until the new windows fully appear and can determine the visibility,
-            // to avoid flickering.
-            mFindResults.setUseTopWallpaperAsTarget(true);
-        }
-
         final WindowContainer animatingContainer = w.mActivityRecord != null
                 ? w.mActivityRecord.getAnimatingContainer() : null;
         final boolean keyguardGoingAwayWithWallpaper = (animatingContainer != null
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c11391e..2596533 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -69,10 +69,6 @@
     SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2);
     private boolean mInitialized = false;
 
-    // When set to true the animator will go over all windows after an animation frame is posted and
-    // check if some got replaced and can be removed.
-    private boolean mRemoveReplacedWindows = false;
-
     private Choreographer mChoreographer;
 
     /**
@@ -217,11 +213,6 @@
         mService.closeSurfaceTransaction("WindowAnimator");
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
 
-        if (mRemoveReplacedWindows) {
-            root.removeReplacedWindows();
-            mRemoveReplacedWindows = false;
-        }
-
         mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         executeAfterPrepareSurfacesRunnables();
 
@@ -286,10 +277,6 @@
         return displayAnimator;
     }
 
-    void requestRemovalOfReplacedWindows(WindowState win) {
-        mRemoveReplacedWindows = true;
-    }
-
     void scheduleAnimation() {
         if (!mAnimationFrameCallbackScheduled) {
             mAnimationFrameCallbackScheduled = true;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 2f3a70e..969afe5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -740,7 +740,7 @@
     /**
      * Show IME on imeTargetWindow once IME has finished layout.
      *
-     * @param imeTargetWindowToken token of the (IME target) window on which IME should be shown.
+     * @param imeTargetWindowToken token of the (IME target) window which IME should be shown.
      * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
     public abstract void showImePostLayout(IBinder imeTargetWindowToken,
@@ -749,7 +749,7 @@
     /**
      * Hide IME using imeTargetWindow when requested.
      *
-     * @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
+     * @param imeTargetWindowToken token of the (IME target) window on which requests hiding IME.
      * @param displayId the id of the display the IME is on.
      * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 42d23e7..be42f36 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,7 +88,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
@@ -382,9 +381,6 @@
     /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
     static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
 
-    /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */
-    static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000;
-
     /** Amount of time to allow a last ANR message to exist before freeing the memory. */
     static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours
 
@@ -565,12 +561,6 @@
     final WindowManagerGlobalLock mGlobalLock;
 
     /**
-     * List of app window tokens that are waiting for replacing windows. If the
-     * replacement doesn't come in time the stale windows needs to be disposed of.
-     */
-    final ArrayList<ActivityRecord> mWindowReplacementTimeouts = new ArrayList<>();
-
-    /**
      * Windows that are being resized.  Used so we can tell the client about
      * the resize after closing the transaction in which we resized the
      * underlying surface.
@@ -1774,15 +1764,6 @@
             final WindowStateAnimator winAnimator = win.mWinAnimator;
             winAnimator.mEnterAnimationPending = true;
             winAnimator.mEnteringAnimation = true;
-            // Check if we need to prepare a transition for replacing window first.
-            if (!win.mTransitionController.isShellTransitionsEnabled()
-                    && activity != null && activity.isVisible()
-                    && !prepareWindowReplacementTransition(activity)) {
-                // If not, check if need to set up a dummy transition during display freeze
-                // so that the unfreeze wait for the apps to draw. This might be needed if
-                // the app is relaunching.
-                prepareNoneTransitionForRelaunching(activity);
-            }
 
             if (displayPolicy.areSystemBarsForcedConsumedLw()) {
                 res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
@@ -1944,48 +1925,6 @@
     }
 
     /**
-     * Returns true if we're done setting up any transitions.
-     */
-    private boolean prepareWindowReplacementTransition(ActivityRecord activity) {
-        activity.clearAllDrawn();
-        final WindowState replacedWindow = activity.getReplacingWindow();
-        if (replacedWindow == null) {
-            // We expect to already receive a request to remove the old window. If it did not
-            // happen, let's just simply add a window.
-            return false;
-        }
-        // We use the visible frame, because we want the animation to morph the window from what
-        // was visible to the user to the final destination of the new window.
-        final Rect frame = new Rect(replacedWindow.getFrame());
-        final WindowManager.LayoutParams attrs = replacedWindow.mAttrs;
-        frame.inset(replacedWindow.getInsetsStateWithVisibilityOverride().calculateVisibleInsets(
-                frame, attrs.type, replacedWindow.getWindowingMode(), attrs.softInputMode,
-                attrs.flags));
-        // We treat this as if this activity was opening, so we can trigger the app transition
-        // animation and piggy-back on existing transition animation infrastructure.
-        final DisplayContent dc = activity.getDisplayContent();
-        dc.mOpeningApps.add(activity);
-        dc.prepareAppTransition(TRANSIT_RELAUNCH);
-        dc.mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top,
-                frame.width(), frame.height());
-        dc.executeAppTransition();
-        return true;
-    }
-
-    private void prepareNoneTransitionForRelaunching(ActivityRecord activity) {
-        // Set up a none-transition and add the app to opening apps, so that the display
-        // unfreeze wait for the apps to be drawn.
-        // Note that if the display unfroze already because app unfreeze timed out,
-        // we don't set up the transition anymore and just let it go.
-        final DisplayContent dc = activity.getDisplayContent();
-        if (mDisplayFrozen && !dc.mOpeningApps.contains(activity) && activity.isRelaunching()) {
-            dc.mOpeningApps.add(activity);
-            dc.prepareAppTransition(TRANSIT_NONE);
-            dc.executeAppTransition();
-        }
-    }
-
-    /**
      * Set whether screen capture is disabled for all windows of a specific user from
      * the device policy cache.
      */
@@ -2390,12 +2329,6 @@
 
             // If we are not currently running the exit animation, we need to see about starting
             // one.
-            // We don't want to animate visibility of windows which are pending replacement.
-            // In the case of activity relaunch child windows could request visibility changes as
-            // they are detached from the main application window during the tear down process.
-            // If we satisfied these visibility changes though, we would cause a visual glitch
-            // hiding the window before it's replacement was available. So we just do nothing on
-            // our side.
             // This must be called before the call to performSurfacePlacement.
             if (!shouldRelayout && winAnimator.hasSurface() && !win.mAnimatingExit) {
                 if (DEBUG_VISIBILITY) {
@@ -2403,20 +2336,18 @@
                             "Relayout invis " + win + ": mAnimatingExit=" + win.mAnimatingExit);
                 }
                 result |= RELAYOUT_RES_SURFACE_CHANGED;
-                if (!win.mWillReplaceWindow) {
-                    // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag
-                    // in DC#pendingLayoutChanges and update the wallpaper target later.
-                    // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window
-                    // when the window is about to exit, so we update the wallpaper target
-                    // immediately here. Otherwise this window will be stuck in exiting and its
-                    // surface remains on the screen.
-                    // TODO(b/189856716): Allow destroying surface even if it belongs to the
-                    //  keyguard target.
-                    if (wallpaperMayMove) {
-                        displayContent.mWallpaperController.adjustWallpaperWindows();
-                    }
-                    tryStartExitingAnimation(win, winAnimator);
+                // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag
+                // in DC#pendingLayoutChanges and update the wallpaper target later.
+                // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window
+                // when the window is about to exit, so we update the wallpaper target
+                // immediately here. Otherwise this window will be stuck in exiting and its
+                // surface remains on the screen.
+                // TODO(b/189856716): Allow destroying surface even if it belongs to the
+                //  keyguard target.
+                if (wallpaperMayMove) {
+                    displayContent.mWallpaperController.adjustWallpaperWindows();
                 }
+                tryStartExitingAnimation(win, winAnimator);
             }
 
             // Create surfaceControl before surface placement otherwise layout will be skipped
@@ -5328,8 +5259,6 @@
 
         public static final int UPDATE_MULTI_WINDOW_STACKS = 41;
 
-        public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
-
         public static final int UPDATE_ANIMATION_SCALE = 51;
         public static final int WINDOW_HIDE_TIMEOUT = 52;
         public static final int RESTORE_POINTER_ICON = 55;
@@ -5555,16 +5484,6 @@
                     }
                     break;
                 }
-                case WINDOW_REPLACEMENT_TIMEOUT: {
-                    synchronized (mGlobalLock) {
-                        for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) {
-                            final ActivityRecord activity = mWindowReplacementTimeouts.get(i);
-                            activity.onWindowReplacementTimeout();
-                        }
-                        mWindowReplacementTimeouts.clear();
-                    }
-                    break;
-                }
                 case WINDOW_HIDE_TIMEOUT: {
                     final WindowState window = (WindowState) msg.obj;
                     synchronized (mGlobalLock) {
@@ -7083,98 +7002,6 @@
         return mGlobalLock;
     }
 
-    /**
-     * Hint to a token that its activity will relaunch, which will trigger removal and addition of
-     * a window.
-     *
-     * @param token Application token for which the activity will be relaunched.
-     */
-    void setWillReplaceWindow(IBinder token, boolean animate) {
-        final ActivityRecord activity = mRoot.getActivityRecord(token);
-        if (activity == null) {
-            ProtoLog.w(WM_ERROR, "Attempted to set replacing window on non-existing app token %s",
-                    token);
-            return;
-        }
-        if (!activity.hasContentToDisplay()) {
-            ProtoLog.w(WM_ERROR,
-                    "Attempted to set replacing window on app token with no content %s",
-                    token);
-            return;
-        }
-        activity.setWillReplaceWindows(animate);
-    }
-
-    /**
-     * Hint to a token that its windows will be replaced across activity relaunch.
-     * The windows would otherwise be removed  shortly following this as the
-     * activity is torn down.
-     * @param token Application token for which the activity will be relaunched.
-     * @param childrenOnly Whether to mark only child windows for replacement
-     *                     (for the case where main windows are being preserved/
-     *                     reused rather than replaced).
-     *
-     */
-    // TODO: The s at the end of the method name is the only difference with the name of the method
-    // above. We should combine them or find better names.
-    void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
-        synchronized (mGlobalLock) {
-            final ActivityRecord activity = mRoot.getActivityRecord(token);
-            if (activity == null) {
-                ProtoLog.w(WM_ERROR,
-                        "Attempted to set replacing window on non-existing app token %s",
-                        token);
-                return;
-            }
-            if (!activity.hasContentToDisplay()) {
-                ProtoLog.w(WM_ERROR,
-                        "Attempted to set replacing window on app token with no content %s",
-                        token);
-                return;
-            }
-
-            if (childrenOnly) {
-                activity.setWillReplaceChildWindows();
-            } else {
-                activity.setWillReplaceWindows(false /* animate */);
-            }
-
-            scheduleClearWillReplaceWindows(token, true /* replacing */);
-        }
-    }
-
-    /**
-     * If we're replacing the window, schedule a timer to clear the replaced window
-     * after a timeout, in case the replacing window is not coming.
-     *
-     * If we're not replacing the window, clear the replace window settings of the app.
-     *
-     * @param token     Application token for the activity whose window might be replaced.
-     * @param replacing Whether the window is being replaced or not.
-     */
-    void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
-        final ActivityRecord activity = mRoot.getActivityRecord(token);
-        if (activity == null) {
-            ProtoLog.w(WM_ERROR, "Attempted to reset replacing window on non-existing app token %s",
-                    token);
-            return;
-        }
-        if (replacing) {
-            scheduleWindowReplacementTimeouts(activity);
-        } else {
-            activity.clearWillReplaceWindows();
-        }
-    }
-
-    void scheduleWindowReplacementTimeouts(ActivityRecord activity) {
-        if (!mWindowReplacementTimeouts.contains(activity)) {
-            mWindowReplacementTimeouts.add(activity);
-        }
-        mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
-        mH.sendEmptyMessageDelayed(
-                H.WINDOW_REPLACEMENT_TIMEOUT, WINDOW_REPLACEMENT_TIMEOUT_DURATION);
-    }
-
     @Override
     public int getDockedStackSide() {
         return 0;
@@ -8744,8 +8571,8 @@
      */
     void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
             SurfaceControl surface, IWindow window, IBinder hostInputToken,
-            int flags, int privateFlags, int type, IBinder windowToken, IBinder focusGrantToken,
-            String inputHandleName, InputChannel outInputChannel) {
+            int flags, int privateFlags, int inputFeatures, int type, IBinder windowToken,
+            IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) {
         final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
         final InputApplicationHandle applicationHandle;
         final String name;
@@ -8762,7 +8589,7 @@
         }
 
         updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
-                name, applicationHandle, flags, privateFlags, sanitizedType,
+                name, applicationHandle, flags, privateFlags, inputFeatures, sanitizedType,
                 null /* region */, window);
 
         clientChannel.copyTo(outInputChannel);
@@ -8803,13 +8630,14 @@
     private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid,
             int displayId, SurfaceControl surface, String name,
             InputApplicationHandle applicationHandle, int flags,
-            int privateFlags, int type, Region region, IWindow window) {
+            int privateFlags, int inputFeatures, int type, Region region, IWindow window) {
         final InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId);
         h.token = channelToken;
         h.setWindowToken(window);
         h.name = name;
 
         flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
+        inputFeatures = sanitizeSpyWindow(inputFeatures, name, callingUid, callingPid);
 
         final int sanitizedLpFlags =
                 (flags & (FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE))
@@ -8819,7 +8647,7 @@
 
         // Do not allow any input features to be set without sanitizing them first.
         h.inputConfig = InputConfigAdapter.getInputConfigFromWindowParams(
-                        type, sanitizedLpFlags, 0 /*inputFeatures*/);
+                        type, sanitizedLpFlags, inputFeatures);
 
 
         if ((flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
@@ -8856,7 +8684,7 @@
      * is undefined.
      */
     void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface,
-            int flags, int privateFlags, Region region) {
+            int flags, int privateFlags, int inputFeatures, Region region) {
         final InputApplicationHandle applicationHandle;
         final String name;
         final EmbeddedWindowController.EmbeddedWindow win;
@@ -8871,7 +8699,8 @@
         }
 
         updateInputChannel(channelToken, win.mOwnerUid, win.mOwnerPid, displayId, surface, name,
-                applicationHandle, flags, privateFlags, win.mWindowType, region, win.mClient);
+                applicationHandle, flags, privateFlags, inputFeatures, win.mWindowType, region,
+                win.mClient);
     }
 
     /** Return whether layer tracing is enabled */
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d6c0311..8a083aa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -59,13 +59,11 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -632,22 +630,6 @@
 
     boolean mHasSurface = false;
 
-    // This window will be replaced due to relaunch. This allows window manager
-    // to differentiate between simple removal of a window and replacement. In the latter case it
-    // will preserve the old window until the new one is drawn.
-    boolean mWillReplaceWindow = false;
-    // If true, the replaced window was already requested to be removed.
-    private boolean mReplacingRemoveRequested = false;
-    // Whether the replacement of the window should trigger app transition animation.
-    private boolean mAnimateReplacingWindow = false;
-    // If not null, the window that will be used to replace the old one. This is being set when
-    // the window is added and unset when this window reports its first draw.
-    private WindowState mReplacementWindow = null;
-    // For the new window in the replacement transition, if we have
-    // requested to replace without animation, then we should
-    // make sure we also don't apply an enter animation for
-    // the new window.
-    boolean mSkipEnterAnimationForSeamlessReplacement = false;
     // Whether this window is being moved via the resize API
     private boolean mMovedByResize;
 
@@ -1309,13 +1291,6 @@
     }
 
     boolean skipLayout() {
-        if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
-            // This window is being replaced and either already got information that it's being
-            // removed or we are still waiting for some information. Because of this we don't
-            // want to apply any more changes to it, so it remains in this state until new window
-            // appears.
-            return true;
-        }
         // Skip layout of the window when in transition to pip mode.
         return mActivityRecord != null && mActivityRecord.mWaitForEnteringPinnedMode;
     }
@@ -2354,24 +2329,6 @@
         }
     }
 
-    void onWindowReplacementTimeout() {
-        if (mWillReplaceWindow) {
-            // Since the window already timed out, remove it immediately now.
-            // Use WindowState#removeImmediately() instead of WindowState#removeIfPossible(), as
-            // the latter delays removal on certain conditions, which will leave the stale window
-            // in the root task and marked mWillReplaceWindow=false, so the window will never be
-            // removed.
-            //
-            // Also removes child windows.
-            removeImmediately();
-        } else {
-            for (int i = mChildren.size() - 1; i >= 0; --i) {
-                final WindowState c = mChildren.get(i);
-                c.onWindowReplacementTimeout();
-            }
-        }
-    }
-
     @Override
     void removeImmediately() {
         if (mRemoved) {
@@ -2388,11 +2345,6 @@
         mWinAnimator.destroySurfaceLocked(getSyncTransaction());
         super.removeImmediately();
 
-        mWillReplaceWindow = false;
-        if (mReplacementWindow != null) {
-            mReplacementWindow.mSkipEnterAnimationForSeamlessReplacement = false;
-        }
-
         final DisplayContent dc = getDisplayContent();
         if (isImeLayeringTarget()) {
             // Remove the attached IME screenshot surface.
@@ -2472,12 +2424,11 @@
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
                             + "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
-                            + "mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s",
+                            + "mDisplayFrozen=%b callers=%s",
                     this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
                     mHasSurface, mWinAnimator.getShown(),
                     isAnimating(TRANSITION | PARENTS),
                     mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
-                    mWillReplaceWindow,
                     mWmService.mDisplayFrozen, Debug.getCallers(6));
 
             // Visibility of the removed window. Will be used later to update orientation later on.
@@ -2487,22 +2438,6 @@
             // window until the animation is done. If the display is frozen, just remove immediately,
             // since the animation wouldn't be seen.
             if (mHasSurface && mToken.okToAnimate()) {
-                if (mWillReplaceWindow) {
-                    // This window is going to be replaced. We need to keep it around until the new one
-                    // gets added, then we will get rid of this one.
-                    ProtoLog.v(WM_DEBUG_ADD_REMOVE,
-                            "Preserving %s until the new one is added", this);
-                    // TODO: We are overloading mAnimatingExit flag to prevent the window state from
-                    // been removed. We probably need another flag to indicate that window removal
-                    // should be deffered vs. overloading the flag that says we are playing an exit
-                    // animation.
-                    ProtoLog.v(WM_DEBUG_ANIM,
-                            "Set animatingExit: reason=remove/replaceWindow win=%s", this);
-                    mAnimatingExit = true;
-                    mReplacingRemoveRequested = true;
-                    return;
-                }
-
                 // If we are not currently running the exit animation, we need to see about starting one
                 wasVisible = isVisible();
 
@@ -2714,53 +2649,6 @@
         mInputWindowHandle.setToken(null);
     }
 
-    /** Returns true if the replacement window was removed. */
-    boolean removeReplacedWindowIfNeeded(WindowState replacement) {
-        if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawn()) {
-            replacement.mSkipEnterAnimationForSeamlessReplacement = false;
-            removeReplacedWindow();
-            return true;
-        }
-
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            if (c.removeReplacedWindowIfNeeded(replacement)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void removeReplacedWindow() {
-        ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Removing replaced window: %s", this);
-        mWillReplaceWindow = false;
-        mAnimateReplacingWindow = false;
-        mReplacingRemoveRequested = false;
-        mReplacementWindow = null;
-        if (mAnimatingExit || !mAnimateReplacingWindow) {
-            removeImmediately();
-        }
-    }
-
-    boolean setReplacementWindowIfNeeded(WindowState replacementCandidate) {
-        boolean replacementSet = false;
-
-        if (mWillReplaceWindow && mReplacementWindow == null
-                && getWindowTag().toString().equals(replacementCandidate.getWindowTag().toString())) {
-
-            mReplacementWindow = replacementCandidate;
-            replacementCandidate.mSkipEnterAnimationForSeamlessReplacement = !mAnimateReplacingWindow;
-            replacementSet = true;
-        }
-
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            replacementSet |= c.setReplacementWindowIfNeeded(replacementCandidate);
-        }
-
-        return replacementSet;
-    }
-
     void setDisplayLayoutNeeded() {
         final DisplayContent dc = getDisplayContent();
         if (dc != null) {
@@ -4391,49 +4279,6 @@
         return parent != null && parent.isGoneForLayout();
     }
 
-    void setWillReplaceWindow(boolean animate) {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            c.setWillReplaceWindow(animate);
-        }
-
-        if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) != 0
-                || mAttrs.type == TYPE_APPLICATION_STARTING) {
-            // We don't set replacing on starting windows since they are added by window manager and
-            // not the client so won't be replaced by the client.
-            return;
-        }
-
-        mWillReplaceWindow = true;
-        mReplacementWindow = null;
-        mAnimateReplacingWindow = animate;
-    }
-
-    void clearWillReplaceWindow() {
-        mWillReplaceWindow = false;
-        mReplacementWindow = null;
-        mAnimateReplacingWindow = false;
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            c.clearWillReplaceWindow();
-        }
-    }
-
-    boolean waitingForReplacement() {
-        if (mWillReplaceWindow) {
-            return true;
-        }
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            if (c.waitingForReplacement()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     void requestUpdateWallpaperIfNeeded() {
         final DisplayContent dc = getDisplayContent();
         if (dc != null && ((mIsWallpaper && !mLastConfigReportedToClient) || hasWallpaper())) {
@@ -4464,43 +4309,6 @@
         return winY;
     }
 
-    // During activity relaunch due to resize, we sometimes use window replacement
-    // for only child windows (as the main window is handled by window preservation)
-    // and the big surface.
-    //
-    // Though windows of TYPE_APPLICATION or TYPE_DRAWN_APPLICATION (as opposed to
-    // TYPE_BASE_APPLICATION) are not children in the sense of an attached window,
-    // we also want to replace them at such phases, as they won't be covered by window
-    // preservation, and in general we expect them to return following relaunch.
-    boolean shouldBeReplacedWithChildren() {
-        return mIsChildWindow || mAttrs.type == TYPE_APPLICATION
-                || mAttrs.type == TYPE_DRAWN_APPLICATION;
-    }
-
-    void setWillReplaceChildWindows() {
-        if (shouldBeReplacedWithChildren()) {
-            setWillReplaceWindow(false /* animate */);
-        }
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            c.setWillReplaceChildWindows();
-        }
-    }
-
-    WindowState getReplacingWindow() {
-        if (mAnimatingExit && mWillReplaceWindow && mAnimateReplacingWindow) {
-            return this;
-        }
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            final WindowState replacing = c.getReplacingWindow();
-            if (replacing != null) {
-                return replacing;
-            }
-        }
-        return null;
-    }
-
     int getRotationAnimationHint() {
         if (mActivityRecord != null) {
             return mActivityRecord.mRotationAnimationHint;
@@ -4990,14 +4798,11 @@
 
     boolean clearAnimatingFlags() {
         boolean didSomething = false;
-        // We don't want to clear it out for windows that get replaced, because the
-        // animation depends on the flag to remove the replaced window.
-        //
         // We also don't clear the mAnimatingExit flag for windows which have the
         // mRemoveOnExit flag. This indicates an explicit remove request has been issued
         // by the client. We should let animation proceed and not clear this flag or
         // they won't eventually be removed by WindowStateAnimator#finishExit.
-        if (!mWillReplaceWindow && !mRemoveOnExit) {
+        if (!mRemoveOnExit) {
             // Clear mAnimating flag together with mAnimatingExit. When animation
             // changes from exiting to entering, we need to clear this flag until the
             // new animation gets applied, so that isAnimationStarting() becomes true
@@ -5312,7 +5117,7 @@
                 return activity.needsZBoost();
             }
         }
-        return mWillReplaceWindow;
+        return false;
     }
 
     private boolean isStartingWindowAssociatedToTask() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e8625bc..3aac816 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -449,7 +449,6 @@
             if (prepared && mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
                     mSurfaceController.showRobustly(t);
-                    mAnimator.requestRemovalOfReplacedWindows(w);
                     mLastHidden = false;
                     final DisplayContent displayContent = w.getDisplayContent();
                     if (!displayContent.getLastHasContent()) {
@@ -504,13 +503,6 @@
     }
 
     void applyEnterAnimationLocked() {
-        // If we are the new part of a window replacement transition and we have requested
-        // not to animate, we instead want to make it seamless, so we don't want to apply
-        // an enter transition.
-        if (mWin.mSkipEnterAnimationForSeamlessReplacement) {
-            return;
-        }
-
         final int transit;
         if (mEnterAnimationPending) {
             mEnterAnimationPending = false;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index c6b7898..327483e 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -315,17 +315,6 @@
         return mChildren.isEmpty();
     }
 
-    WindowState getReplacingWindow() {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState win = mChildren.get(i);
-            final WindowState replacing = win.getReplacingWindow();
-            if (replacing != null) {
-                return replacing;
-            }
-        }
-        return null;
-    }
-
     /** Return true if this token has a window that wants the wallpaper displayed behind it. */
     boolean windowsCanBeWallpaperTarget() {
         for (int j = mChildren.size() - 1; j >= 0; j--) {
diff --git a/services/core/jni/gnss/GnssConfiguration.cpp b/services/core/jni/gnss/GnssConfiguration.cpp
index 3677641..b57e451 100644
--- a/services/core/jni/gnss/GnssConfiguration.cpp
+++ b/services/core/jni/gnss/GnssConfiguration.cpp
@@ -67,7 +67,7 @@
       : mIGnssConfiguration(iGnssConfiguration) {}
 
 jobject GnssConfiguration::getVersion(JNIEnv* env) {
-    return createHalInterfaceVersionJavaObject(env, 3, 0);
+    return createHalInterfaceVersionJavaObject(env, 3, mIGnssConfiguration->getInterfaceVersion());
 }
 
 jboolean GnssConfiguration::setEmergencySuplPdn(jint enable) {
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 793d83e..4f8235a 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -168,7 +168,7 @@
 
     private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
         Log.i(TAG, "respondToClientWithErrorAndFinish");
-
+        // TODO add exception bit
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
             Log.i(TAG, "Request has already been completed. This is strange.");
             return;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 4c5c366..85a48d9 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -592,9 +592,13 @@
         }
 
         private void finalizeAndEmitInitialPhaseMetric(RequestSession session) {
-            var initMetric = session.mInitialPhaseMetric;
-            initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
-            MetricUtilities.logApiCalled(initMetric);
+            try {
+                var initMetric = session.mInitialPhaseMetric;
+                initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
+                MetricUtilities.logApiCalled(initMetric);
+            } catch (Exception e) {
+                Log.w(TAG, "Unexpected error during metric logging: " + e);
+            }
         }
 
         @Override
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index ed139b5..99f3b3e 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -81,6 +81,34 @@
     }
 
     /**
+     * A logging utility used primarily for the candidate phase of the current metric setup.
+     *
+     * @param providers            a map with known providers
+     * @param emitSequenceId       an emitted sequence id for the current session
+     */
+    protected static void logApiCalled(Map<String, ProviderSession> providers,
+            int emitSequenceId) {
+        try {
+            var providerSessions = providers.values();
+            int providerSize = providerSessions.size();
+            int[] candidateUidList = new int[providerSize];
+            int[] candidateQueryRoundTripTimeList = new int[providerSize];
+            int[] candidateStatusList = new int[providerSize];
+            int index = 0;
+            for (var session : providerSessions) {
+                CandidatePhaseMetric metric = session.mCandidatePhasePerProviderMetric;
+                candidateUidList[index] = metric.getCandidateUid();
+                candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
+                candidateStatusList[index] = metric.getProviderQueryStatus();
+                index++;
+            }
+            // TODO Handle the emit here
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
      * The most common logging helper, handles the overall status of the API request with the
      * provider status and latencies. Other versions of this method may be more useful depending
      * on the situation, as this is geared towards the logging of {@link ProviderSession} types.
@@ -90,6 +118,7 @@
      * @param providers            a map with known providers
      * @param callingUid           the calling UID of the client app
      * @param chosenProviderFinalPhaseMetric the metric data type of the final chosen provider
+     * TODO remove soon
      */
     protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
             Map<String, ProviderSession> providers, int callingUid,
@@ -133,6 +162,7 @@
      * contain default values for all other optional parameters.
      *
      * TODO(b/271135048) - given space requirements, this may be a good candidate for another atom
+     * TODO immediately remove and carry over TODO to new log for this setup
      *
      * @param apiName    the api name to log
      * @param apiStatus  the status to log
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 950cf4f..b86daba 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -33,7 +33,7 @@
  *
  * @hide
  */
-public final class  ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
+public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
         Void>
         implements
         RemoteCredentialService.ProviderCallbacks<Void> {
@@ -42,7 +42,8 @@
     private ClearCredentialStateException mProviderException;
 
     /** Creates a new provider session to be used by the request session. */
-    @Nullable public static ProviderClearSession createNewSession(
+    @Nullable
+    public static ProviderClearSession createNewSession(
             Context context,
             @UserIdInt int userId,
             CredentialProviderInfo providerInfo,
@@ -53,7 +54,7 @@
                         clearRequestSession.mClientRequest,
                         clearRequestSession.mClientAppInfo);
         return new ProviderClearSession(context, providerInfo, clearRequestSession, userId,
-                    remoteCredentialService, providerRequest);
+                remoteCredentialService, providerRequest);
     }
 
     @Nullable
@@ -90,6 +91,7 @@
         if (exception instanceof ClearCredentialStateException) {
             mProviderException = (ClearCredentialStateException) exception;
         }
+        captureCandidateFailure();
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
@@ -120,14 +122,7 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            /*
-            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
-            TODO immediately once the other change patched through
-            mCandidateProviderMetric.setSessionId(initMetric
-            .mInitialPhaseMetric.getSessionId());
-            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
-             */
-            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            startCandidateMetrics();
             mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 3ec0fc0..bbbb156 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -40,6 +40,8 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.server.credentials.metrics.EntryEnum;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -65,7 +67,8 @@
     private final ProviderResponseDataHandler mProviderResponseDataHandler;
 
     /** Creates a new provider session to be used by the request session. */
-    @Nullable public static ProviderCreateSession createNewSession(
+    @Nullable
+    public static ProviderCreateSession createNewSession(
             Context context,
             @UserIdInt int userId,
             CredentialProviderInfo providerInfo,
@@ -155,6 +158,7 @@
             // Store query phase exception for aggregation with final response
             mProviderException = (CreateCredentialException) exception;
         }
+        captureCandidateFailure();
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
@@ -175,14 +179,32 @@
         mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
                 response.getRemoteCreateEntry());
         if (mProviderResponseDataHandler.isEmptyResponse(response)) {
+            gatheCandidateEntryMetrics(response);
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
         } else {
+            gatheCandidateEntryMetrics(response);
             updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
         }
     }
 
+    private void gatheCandidateEntryMetrics(BeginCreateCredentialResponse response) {
+        try {
+            var createEntries = response.getCreateEntries();
+            int numCreateEntries = createEntries == null ? 0 : createEntries.size();
+            // TODO confirm how to get types from slice
+            if (numCreateEntries > 0) {
+                createEntries.forEach(c ->
+                        mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+            }
+            mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
     @Override
-    @Nullable protected CreateCredentialProviderData prepareUiData()
+    @Nullable
+    protected CreateCredentialProviderData prepareUiData()
             throws IllegalArgumentException {
         Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
@@ -226,14 +248,7 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            /*
-            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
-            TODO immediately once the other change patched through
-            mCandidateProviderMetric.setSessionId(initMetric
-            .mInitialPhaseMetric.getSessionId());
-            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
-             */
-            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            startCandidateMetrics();
             mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
         }
     }
@@ -305,12 +320,14 @@
     }
 
     private class ProviderResponseDataHandler {
-        @Nullable private final ComponentName mExpectedRemoteEntryProviderService;
+        @Nullable
+        private final ComponentName mExpectedRemoteEntryProviderService;
 
         @NonNull
         private final Map<String, Pair<CreateEntry, Entry>> mUiCreateEntries = new HashMap<>();
 
-        @Nullable private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
+        @Nullable
+        private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
 
         ProviderResponseDataHandler(@Nullable ComponentName expectedRemoteEntryProviderService) {
             mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
@@ -323,6 +340,7 @@
                 setRemoteEntry(remoteEntry);
             }
         }
+
         public void addCreateEntry(CreateEntry createEntry) {
             String id = generateUniqueId();
             Entry entry = new Entry(SAVE_ENTRY_KEY,
@@ -373,6 +391,7 @@
         private boolean isEmptyResponse() {
             return mUiCreateEntries.isEmpty() && mUiRemoteEntry == null;
         }
+
         @Nullable
         public RemoteEntry getRemoteEntry(String entryKey) {
             return mUiRemoteEntry == null || mUiRemoteEntry
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index ec8bf22..bf1db37 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -43,6 +43,8 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.server.credentials.metrics.EntryEnum;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -82,7 +84,8 @@
     private final ProviderResponseDataHandler mProviderResponseDataHandler;
 
     /** Creates a new provider session to be used by the request session. */
-    @Nullable public static ProviderGetSession createNewSession(
+    @Nullable
+    public static ProviderGetSession createNewSession(
             Context context,
             @UserIdInt int userId,
             CredentialProviderInfo providerInfo,
@@ -113,6 +116,7 @@
         Log.i(TAG, "Unable to create provider session");
         return null;
     }
+
     private static BeginGetCredentialRequest constructQueryPhaseRequest(
             android.credentials.GetCredentialRequest filteredRequest,
             CallingAppInfo callingAppInfo,
@@ -169,7 +173,7 @@
             CallingAppInfo callingAppInfo,
             Map<String, CredentialOption> beginGetOptionToCredentialOptionMap,
             String hybridService) {
-        super(context, beginGetRequest, callbacks, info.getComponentName() ,
+        super(context, beginGetRequest, callbacks, info.getComponentName(),
                 userId, remoteCredentialService);
         mCompleteRequest = completeGetRequest;
         mCallingAppInfo = callingAppInfo;
@@ -191,6 +195,7 @@
         if (exception instanceof GetCredentialException) {
             mProviderException = (GetCredentialException) exception;
         }
+        captureCandidateFailure();
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
@@ -269,20 +274,14 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            /*
-            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
-            TODO immediately once the other change patched through
-            mCandidateProviderMetric.setSessionId(initMetric
-            .mInitialPhaseMetric.getSessionId());
-            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
-             */
-            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            startCandidateMetrics();
             mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
         }
     }
 
     @Override // Call from request session to data to be shown on the UI
-    @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
+    @Nullable
+    protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
         Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
             Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
@@ -389,6 +388,7 @@
         GetCredentialException exception = maybeGetPendingIntentException(
                 providerPendingIntentResponse);
         if (exception != null) {
+            // TODO (b/271135048), for AuthenticationEntry callback selection, set error
             invokeCallbackWithError(exception.getType(),
                     exception.getMessage());
             // Additional content received is in the form of an exception which ends the flow.
@@ -439,11 +439,34 @@
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
             return;
         }
-        // TODO immediately, add to Candidate Phase counts, repeat across all sessions
-        // Use sets to dedup type counts
+        gatherCandidateEntryMetrics(response);
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
     }
 
+    private void gatherCandidateEntryMetrics(BeginGetCredentialResponse response) {
+        try {
+            int numCredEntries = response.getCredentialEntries().size();
+            int numActionEntries = response.getActions().size();
+            int numAuthEntries = response.getAuthenticationActions().size();
+            // TODO immediately add remote entries
+            // TODO immediately confirm how to get types from slice to get unique type count via
+            //  dedupe
+            response.getCredentialEntries().forEach(c ->
+                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+            response.getActions().forEach(c ->
+                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
+            response.getAuthenticationActions().forEach(c ->
+                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
+            mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
+                    + numActionEntries);
+            mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
+            mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
+            mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
     /**
      * When an invalid state occurs, e.g. entry mismatch or no response from provider,
      * we send back a TYPE_NO_CREDENTIAL error as to the developer.
@@ -471,11 +494,12 @@
                                 .STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT
                                 || e.second.getStatus()
                                 == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
-        );
+                );
     }
 
     private class ProviderResponseDataHandler {
-        @Nullable private final ComponentName mExpectedRemoteEntryProviderService;
+        @Nullable
+        private final ComponentName mExpectedRemoteEntryProviderService;
         @NonNull
         private final Map<String, Pair<CredentialEntry, Entry>> mUiCredentialEntries =
                 new HashMap<>();
@@ -485,7 +509,8 @@
         private final Map<String, Pair<Action, AuthenticationEntry>> mUiAuthenticationEntries =
                 new HashMap<>();
 
-        @Nullable private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
+        @Nullable
+        private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
 
         ProviderResponseDataHandler(@Nullable ComponentName expectedRemoteEntryProviderService) {
             mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
@@ -509,6 +534,7 @@
                 setRemoteEntry(remoteEntry);
             }
         }
+
         public void addCredentialEntry(CredentialEntry credentialEntry) {
             String id = generateUniqueId();
             Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
@@ -559,7 +585,6 @@
         }
 
 
-
         public GetCredentialProviderData toGetCredentialProviderData() {
             return new GetCredentialProviderData.Builder(
                     mComponentName.flattenToString()).setActionChips(prepareActionEntries())
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 77d4e77..faa91dc 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 
 import com.android.server.credentials.metrics.CandidatePhaseMetric;
+import com.android.server.credentials.metrics.InitialPhaseMetric;
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.UUID;
@@ -72,8 +73,9 @@
     @NonNull
     protected Boolean mProviderResponseSet = false;
     // Specific candidate provider metric for the provider this session handles
-    @Nullable
-    protected CandidatePhaseMetric mCandidatePhasePerProviderMetric;
+    @NonNull
+    protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
+            new CandidatePhaseMetric();
     @NonNull
     private int mProviderSessionUid;
 
@@ -143,7 +145,6 @@
         mUserId = userId;
         mComponentName = componentName;
         mRemoteCredentialService = remoteCredentialService;
-        mCandidatePhasePerProviderMetric = new CandidatePhaseMetric();
         mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
     }
 
@@ -208,6 +209,12 @@
         return mRemoteCredentialService;
     }
 
+    protected void captureCandidateFailure() {
+        mCandidatePhasePerProviderMetric.setHasException(true);
+        // TODO(b/271135048) - this is a true exception, but what about the empty case?
+        // Add more nuance in next iteration.
+    }
+
     /** Updates the status . */
     protected void updateStatusAndInvokeCallback(@NonNull Status status) {
         setStatus(status);
@@ -216,18 +223,37 @@
     }
 
     private void updateCandidateMetric(Status status) {
-        mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
-        // TODO immediately update the candidate phase here to have more new data
-        mCandidatePhasePerProviderMetric
-                .setQueryFinishTimeNanoseconds(System.nanoTime());
-        if (isTerminatingStatus(status)) {
-            mCandidatePhasePerProviderMetric.setProviderQueryStatus(
-                    ProviderStatusForMetrics.QUERY_FAILURE
-                            .getMetricCode());
-        } else if (isCompletionStatus(status)) {
-            mCandidatePhasePerProviderMetric.setProviderQueryStatus(
-                    ProviderStatusForMetrics.QUERY_SUCCESS
-                            .getMetricCode());
+        try {
+            mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
+            // TODO immediately update the candidate phase here to have more new data
+            mCandidatePhasePerProviderMetric
+                    .setQueryFinishTimeNanoseconds(System.nanoTime());
+            if (isTerminatingStatus(status)) {
+                mCandidatePhasePerProviderMetric.setQueryReturned(false);
+                mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                        ProviderStatusForMetrics.QUERY_FAILURE
+                                .getMetricCode());
+            } else if (isCompletionStatus(status)) {
+                mCandidatePhasePerProviderMetric.setQueryReturned(true);
+                mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                        ProviderStatusForMetrics.QUERY_SUCCESS
+                                .getMetricCode());
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    // Common method to transfer metrics from the initial phase to the candidate phase per provider
+    protected void startCandidateMetrics() {
+        try {
+            InitialPhaseMetric initMetric = ((RequestSession) mCallbacks).mInitialPhaseMetric;
+            mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId());
+            mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds(
+                    initMetric.getCredentialServiceStartedTimeNanoseconds());
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 5dc7adf..3ac10c9 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -78,8 +78,8 @@
     protected final CancellationSignal mCancellationSignal;
 
     protected final Map<String, ProviderSession> mProviders = new HashMap<>();
-    protected InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
-    protected ChosenProviderFinalPhaseMetric
+    protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
+    protected final ChosenProviderFinalPhaseMetric
             mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
 
     // TODO(b/271135048) - Group metrics used in a scope together, such as here in RequestSession
@@ -123,9 +123,17 @@
                 mUserId, this);
         mHybridService = context.getResources().getString(
                 R.string.config_defaultCredentialManagerHybridService);
-        mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
-        mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
-        mInitialPhaseMetric.setCallerUid(mCallingUid);
+        initialPhaseMetricSetup(timestampStarted);
+    }
+
+    private void initialPhaseMetricSetup(long timestampStarted) {
+        try {
+            mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
+            mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
+            mInitialPhaseMetric.setCallerUid(mCallingUid);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
     }
 
     public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
@@ -170,13 +178,17 @@
 
     private void logBrowsingPhasePerSelect(UserSelectionDialogResult selection,
             ProviderSession providerSession) {
-        CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
-        browsingPhaseMetric.setSessionId(this.mInitialPhaseMetric.getSessionId());
-        browsingPhaseMetric.setEntryEnum(
-                EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
-        browsingPhaseMetric.setProviderUid(providerSession.mCandidatePhasePerProviderMetric
-                .getCandidateUid());
-        this.mCandidateBrowsingPhaseMetric.add(new CandidateBrowsingPhaseMetric());
+        try {
+            CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
+            browsingPhaseMetric.setSessionId(this.mInitialPhaseMetric.getSessionId());
+            browsingPhaseMetric.setEntryEnum(
+                    EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
+            browsingPhaseMetric.setProviderUid(providerSession.mCandidatePhasePerProviderMetric
+                    .getCandidateUid());
+            this.mCandidateBrowsingPhaseMetric.add(new CandidateBrowsingPhaseMetric());
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
     }
 
     protected void finishSession(boolean propagateCancellation) {
@@ -233,6 +245,7 @@
         Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
 
         if (isSessionCancelled()) {
+            MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
             finishSession(/*propagateCancellation=*/true);
             return;
         }
@@ -248,13 +261,8 @@
         }
         if (!providerDataList.isEmpty()) {
             Log.i(TAG, "provider list not empty about to initiate ui");
-            // TODO immediately Add paths to end it (say it fails)
-            if (isSessionCancelled()) {
-                Log.i(TAG, "In getProviderDataAndInitiateUi but session has been cancelled");
-                // TODO immedaitely Add paths
-            } else {
-                launchUiWithProviderData(providerDataList);
-            }
+            MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
+            launchUiWithProviderData(providerDataList);
         }
     }
 
@@ -264,22 +272,27 @@
      * @param componentName the componentName to associate with a provider
      */
     protected void setChosenMetric(ComponentName componentName) {
-        CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
-                .mCandidatePhasePerProviderMetric;
+        try {
+            CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
+                    .mCandidatePhasePerProviderMetric;
 
-        mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
-        mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
+            mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
+            mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
 
-        mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
-                metric.getQueryLatencyMicroseconds());
+            mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
+                    metric.getQueryLatencyMicroseconds());
 
-        mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
-                metric.getServiceBeganTimeNanoseconds());
-        mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
-                metric.getStartQueryTimeNanoseconds());
+            mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
+                    metric.getServiceBeganTimeNanoseconds());
+            mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
+                    metric.getStartQueryTimeNanoseconds());
 
-        // TODO immediately update with the entry count numbers from the candidate metrics
+            // TODO immediately update with the entry count numbers from the candidate metrics
+            // TODO immediately add the exception bit for candidates and providers
 
-        mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
+            mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index c392d78..f00c7f4 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -20,6 +20,9 @@
 
 import com.android.server.credentials.MetricUtilities;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * The central candidate provider metric object that mimics our defined metric setup.
  * Some types are redundant across these metric collectors, but that has debug use-cases as
@@ -66,6 +69,8 @@
     private int mRemoteEntryCount = -1;
     // The count of authentication entries from this provider, defaults to -1
     private int mAuthenticationEntryCount = -1;
+    // Gathered to pass on to chosen provider when required
+    private List<EntryEnum> mAvailableEntries = new ArrayList<>();
 
     public CandidatePhaseMetric() {
     }
@@ -236,4 +241,28 @@
     public int getAuthenticationEntryCount() {
         return mAuthenticationEntryCount;
     }
+
+    /* -------------- The Entries Gathered ---------------- */
+
+    /**
+     * Allows adding an entry record to this metric collector, which can then be propagated to
+     * the final phase to retain information on the data available to the candidate.
+     *
+     * @param e the entry enum collected by the candidate provider associated with this metric
+     *          collector
+     */
+    public void addEntry(EntryEnum e) {
+        this.mAvailableEntries.add(e);
+    }
+
+    /**
+     * Returns a safely copied list of the entries captured by this metric collector associated
+     * with a particular candidate provider.
+     *
+     * @return the full collection of entries encountered by the candidate provider associated with
+     * this metric
+     */
+    public List<EntryEnum> getAvailableEntries() {
+        return new ArrayList<>(this.mAvailableEntries); // no alias copy
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a1789b2..720533f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -95,6 +95,7 @@
 import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
 import static android.app.admin.DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY;
 import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_FINANCING_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
 import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
@@ -15440,7 +15441,8 @@
             Slogf.i(LOG_TAG, "Sending %s broadcast to manifest receivers.", intent.getAction());
             broadcastIntentToCrossProfileManifestReceivers(
                     intent, parentHandle, requiresPermission);
-            broadcastIntentToDevicePolicyManagerRoleHolder(intent, parentHandle);
+            broadcastExplicitIntentToRoleHolder(
+                    intent, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, parentHandle);
         }
 
         @Override
@@ -15481,36 +15483,6 @@
             }
         }
 
-        private void broadcastIntentToDevicePolicyManagerRoleHolder(
-                Intent intent, UserHandle userHandle) {
-            final int userId = userHandle.getIdentifier();
-            final String packageName = getDevicePolicyManagementRoleHolderPackageName(mContext);
-            if (packageName == null) {
-                return;
-            }
-            try {
-                final Intent packageIntent = new Intent(intent)
-                        .setPackage(packageName);
-                final List<ResolveInfo> receivers = mIPackageManager.queryIntentReceivers(
-                        packageIntent,
-                        /* resolvedType= */ null,
-                        STOCK_PM_FLAGS,
-                        userId).getList();
-                if (receivers.isEmpty()) {
-                    return;
-                }
-                for (ResolveInfo receiver : receivers) {
-                    final Intent componentIntent = new Intent(packageIntent)
-                            .setComponent(receiver.getComponentInfo().getComponentName())
-                            .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                    mContext.sendBroadcastAsUser(componentIntent, userHandle);
-                }
-            } catch (RemoteException ex) {
-                Slogf.w(LOG_TAG, "Cannot get list of broadcast receivers for %s because: %s.",
-                        intent.getAction(), ex);
-            }
-        }
-
         /**
          * Checks whether the package {@code packageName} has the {@code MODIFY_QUIET_MODE}
          * permission granted for the user {@code userId}.
@@ -20718,7 +20690,7 @@
 
     private void maybeInstallDevicePolicyManagementRoleHolderInUser(int targetUserId) {
         String devicePolicyManagerRoleHolderPackageName =
-                getDevicePolicyManagementRoleHolderPackageName(mContext);
+                getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
         if (devicePolicyManagerRoleHolderPackageName == null) {
             Slogf.d(LOG_TAG, "No device policy management role holder specified.");
             return;
@@ -20744,14 +20716,24 @@
         }
     }
 
+    /**
+     * If multiple packages hold the role, returns the first package in the list.
+     */
+    @Nullable
+    private String getRoleHolderPackageName(Context context, String role) {
+        return getRoleHolderPackageNameOnUser(context, role, Process.myUserHandle());
+    }
 
-    private String getDevicePolicyManagementRoleHolderPackageName(Context context) {
+    /**
+     * If multiple packages hold the role, returns the first package in the list.
+     */
+    @Nullable
+    private String getRoleHolderPackageNameOnUser(Context context, String role, UserHandle user) {
         RoleManager roleManager = context.getSystemService(RoleManager.class);
 
         // Calling identity needs to be cleared as this method is used in the permissions checks.
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            List<String> roleHolders =
-                    roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
+            List<String> roleHolders = roleManager.getRoleHoldersAsUser(role, user);
             if (roleHolders.isEmpty()) {
                 return null;
             }
@@ -20762,7 +20744,7 @@
     private boolean isCallerDevicePolicyManagementRoleHolder(CallerIdentity caller) {
         int callerUid = caller.getUid();
         String devicePolicyManagementRoleHolderPackageName =
-                getDevicePolicyManagementRoleHolderPackageName(mContext);
+                getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
         int roleHolderUid = mInjector.getPackageManagerInternal().getPackageUid(
                 devicePolicyManagementRoleHolderPackageName, 0, caller.getUserId());
 
@@ -21830,15 +21812,23 @@
         }
 
         public void register() {
-            mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.SYSTEM);
+            mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
         }
 
         @Override
         public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
-            if (!RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
+            if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
+                handleDevicePolicyManagementRoleChange(user);
                 return;
             }
-            String newRoleHolder = getRoleHolder();
+            if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) {
+                handleFinancedDeviceKioskRoleChange();
+                return;
+            }
+        }
+
+        private void handleDevicePolicyManagementRoleChange(UserHandle user) {
+            String newRoleHolder = getDeviceManagementRoleHolder(user);
             if (isDefaultRoleHolder(newRoleHolder)) {
                 Slogf.i(LOG_TAG,
                         "onRoleHoldersChanged: Default role holder is set, returning early");
@@ -21873,9 +21863,42 @@
             }
         }
 
-        private String getRoleHolder() {
-            return DevicePolicyManagerService.this.getDevicePolicyManagementRoleHolderPackageName(
-                    mContext);
+        private void handleFinancedDeviceKioskRoleChange() {
+            if (!isDevicePolicyEngineEnabled()) {
+                return;
+            }
+            Slog.i(LOG_TAG, "Handling action " + ACTION_DEVICE_FINANCING_STATE_CHANGED);
+            Intent intent = new Intent(ACTION_DEVICE_FINANCING_STATE_CHANGED);
+            mInjector.binderWithCleanCallingIdentity(() -> {
+                for (UserInfo userInfo : mUserManager.getUsers()) {
+                    UserHandle user = userInfo.getUserHandle();
+                    broadcastExplicitIntentToRoleHolder(
+                            intent, RoleManager.ROLE_SYSTEM_SUPERVISION, user);
+                    broadcastExplicitIntentToRoleHolder(
+                            intent, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, user);
+                    ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(user.getIdentifier());
+                    if (admin == null) {
+                        continue;
+                    }
+                    if (!isProfileOwnerOfOrganizationOwnedDevice(
+                            admin.info.getComponent(), user.getIdentifier())
+                            && !isDeviceOwner(admin)) {
+                        continue;
+                    }
+                    // Don't send the broadcast twice if the DPC is the same package as the
+                    // DMRH
+                    if (admin.info.getPackageName().equals(getDeviceManagementRoleHolder(user))) {
+                        continue;
+                    }
+                    broadcastExplicitIntentToPackage(
+                            intent, admin.info.getPackageName(), admin.getUserHandle());
+                }
+            });
+        }
+
+        private String getDeviceManagementRoleHolder(UserHandle user) {
+            return DevicePolicyManagerService.this.getRoleHolderPackageNameOnUser(
+                    mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, user);
         }
 
         private boolean isDefaultRoleHolder(String packageName) {
@@ -21935,6 +21958,40 @@
         }
     }
 
+    private void broadcastExplicitIntentToRoleHolder(
+            Intent intent, String role, UserHandle userHandle) {
+        String packageName = getRoleHolderPackageNameOnUser(mContext, role, userHandle);
+        if (packageName == null) {
+            return;
+        }
+        broadcastExplicitIntentToPackage(intent, packageName, userHandle);
+    }
+
+    private void broadcastExplicitIntentToPackage(
+            Intent intent, String packageName, UserHandle userHandle) {
+        int userId = userHandle.getIdentifier();
+        if (packageName == null) {
+            return;
+        }
+        Intent packageIntent = new Intent(intent)
+                .setPackage(packageName);
+        List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
+                packageIntent,
+                PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+                userId);
+        if (receivers.isEmpty()) {
+            Slog.i(LOG_TAG, "Found no receivers to handle intent " + intent
+                    + " in package " + packageName);
+            return;
+        }
+        for (ResolveInfo receiver : receivers) {
+            Intent componentIntent = new Intent(packageIntent)
+                    .setComponent(receiver.getComponentInfo().getComponentName())
+                    .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            mContext.sendBroadcastAsUser(componentIntent, userHandle);
+        }
+    }
+
     @Override
     public List<UserHandle> getPolicyManagedProfiles(@NonNull UserHandle user) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 7e44049..7d4f87d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -17,6 +17,7 @@
 package com.android.server.inputmethod;
 
 import static android.inputmethodservice.InputMethodService.IME_ACTIVE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
@@ -35,11 +36,16 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,8 +66,8 @@
         super.setUp();
         mVisibilityApplier =
                 (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
-        mInputMethodManagerService.mCurFocusedWindowClient = mock(
-                InputMethodManagerService.ClientState.class);
+        mInputMethodManagerService.setAttachedClientForTesting(
+                mock(InputMethodManagerService.ClientState.class));
     }
 
     @Test
@@ -119,4 +125,38 @@
         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
         verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT);
     }
+
+    @Test
+    public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() {
+        // Init a IME target client on the secondary display to show IME.
+        mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
+                10 /* selfReportedDisplayId */);
+        mInputMethodManagerService.setAttachedClientForTesting(null);
+        startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+        synchronized (ImfLock.class) {
+            final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
+            // Verify hideIme will apply the expected displayId when the default IME
+            // visibility applier app STATE_HIDE_IME.
+            mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME);
+            verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
+                    eq(mWindowToken), eq(displayIdToShowIme), eq(null));
+        }
+    }
+
+    private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) {
+        return mInputMethodManagerService.startInputOrWindowGainedFocus(
+                StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
+                mMockInputMethodClient /* client */,
+                windowToken /* windowToken */,
+                StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR,
+                softInputMode /* softInputMode */,
+                0 /* windowFlags */,
+                mEditorInfo /* editorInfo */,
+                mMockRemoteInputConnection /* inputConnection */,
+                mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
+                mTargetSdkVersion /* unverifiedTargetSdkVersion */,
+                mCallingUserId /* userId */,
+                mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 5377ee7..3a47b47 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -50,7 +50,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -624,8 +623,7 @@
 
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_INACTIVE);
-        verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
+        verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT));
     }
 
     @Test
@@ -643,8 +641,7 @@
 
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_INACTIVE);
-        verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
+        verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT));
         // The device configuration doesn't require a motion sensor to proceed with idling.
         // This should be the case on TVs or other such devices. We should set an alarm to move
         // forward if the motion sensor is missing in this case.
@@ -669,8 +666,7 @@
 
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_INACTIVE);
-        verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
+        verify(mDeviceIdleController).scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT));
         // The device configuration requires a motion sensor to proceed with idling,
         // so we should never set an alarm to move forward if the motion sensor is
         // missing in this case.
@@ -699,7 +695,7 @@
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_INACTIVE);
         inOrder.verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(timeUntilAlarm + mConstants.INACTIVE_TIMEOUT), eq(false));
+                .scheduleAlarmLocked(eq(timeUntilAlarm + mConstants.INACTIVE_TIMEOUT));
 
         enterDeepState(STATE_ACTIVE);
         setQuickDozeEnabled(true);
@@ -709,7 +705,7 @@
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_QUICK_DOZE_DELAY);
         inOrder.verify(mDeviceIdleController).scheduleAlarmLocked(
-                eq(timeUntilAlarm + mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+                eq(timeUntilAlarm + mConstants.QUICK_DOZE_DELAY_TIMEOUT));
     }
 
     @Test
@@ -736,59 +732,56 @@
         setScreenOn(false);
         verifyStateConditions(STATE_QUICK_DOZE_DELAY);
         inOrder.verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
 
         enterDeepState(STATE_INACTIVE);
         setQuickDozeEnabled(true);
         verifyStateConditions(STATE_QUICK_DOZE_DELAY);
         inOrder.verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
 
         enterDeepState(STATE_IDLE_PENDING);
         setQuickDozeEnabled(true);
         verifyStateConditions(STATE_QUICK_DOZE_DELAY);
         inOrder.verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
 
         enterDeepState(STATE_SENSING);
         setQuickDozeEnabled(true);
         verifyStateConditions(STATE_QUICK_DOZE_DELAY);
         inOrder.verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
 
         enterDeepState(STATE_LOCATING);
         setQuickDozeEnabled(true);
         verifyStateConditions(STATE_QUICK_DOZE_DELAY);
         inOrder.verify(mDeviceIdleController)
-                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT), eq(false));
+                .scheduleAlarmLocked(eq(mConstants.QUICK_DOZE_DELAY_TIMEOUT));
 
         // IDLE should stay as IDLE.
         enterDeepState(STATE_IDLE);
         // Clear out any alarm setting from the order before checking for this section.
-        inOrder.verify(mDeviceIdleController, atLeastOnce())
-                .scheduleAlarmLocked(anyLong(), anyBoolean());
+        inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong());
         setQuickDozeEnabled(true);
         verifyStateConditions(STATE_IDLE);
-        inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean());
+        inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong());
 
         // IDLE_MAINTENANCE should stay as IDLE_MAINTENANCE.
         enterDeepState(STATE_IDLE_MAINTENANCE);
         // Clear out any alarm setting from the order before checking for this section.
-        inOrder.verify(mDeviceIdleController, atLeastOnce())
-                .scheduleAlarmLocked(anyLong(), anyBoolean());
+        inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong());
         setQuickDozeEnabled(true);
         verifyStateConditions(STATE_IDLE_MAINTENANCE);
-        inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean());
+        inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong());
 
         // State is already QUICK_DOZE_DELAY. No work should be done.
         enterDeepState(STATE_QUICK_DOZE_DELAY);
         // Clear out any alarm setting from the order before checking for this section.
-        inOrder.verify(mDeviceIdleController, atLeastOnce())
-                .scheduleAlarmLocked(anyLong(), anyBoolean());
+        inOrder.verify(mDeviceIdleController, atLeastOnce()).scheduleAlarmLocked(anyLong());
         setQuickDozeEnabled(true);
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_QUICK_DOZE_DELAY);
-        inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong(), anyBoolean());
+        inOrder.verify(mDeviceIdleController, never()).scheduleAlarmLocked(anyLong());
     }
 
     @Test
@@ -2685,17 +2678,12 @@
         if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
             enterDeepState(STATE_IDLE);
             long now = SystemClock.elapsedRealtime();
-            long alarm = mDeviceIdleController.getNextAlarmTime();
             mDeviceIdleController.setIdleStartTimeForTest(
                     now - (long) (mConstants.IDLE_TIMEOUT * 0.6));
-            long newAlarm = mDeviceIdleController.getNextAlarmTime();
-            assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6",
-                    newAlarm == alarm);
+            verifyStateConditions(STATE_IDLE);
             mDeviceIdleController.setIdleStartTimeForTest(
                     now - (long) (mConstants.IDLE_TIMEOUT * 1.2));
-            newAlarm = mDeviceIdleController.getNextAlarmTime();
-            assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2",
-                    (newAlarm - now) < minuteInMillis);
+            verifyStateConditions(STATE_IDLE_MAINTENANCE);
             mDeviceIdleController.resetPreIdleTimeoutMode();
         }
     }
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 e7e26a1..1741411 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -28,7 +28,6 @@
 import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
-import static android.app.AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT;
 import static android.app.AlarmManager.WINDOW_EXACT;
 import static android.app.AlarmManager.WINDOW_HEURISTIC;
 import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -126,7 +125,6 @@
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
 import android.app.compat.CompatChanges;
-import android.app.role.RoleManager;
 import android.app.tare.EconomyManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ContentResolver;
@@ -134,7 +132,6 @@
 import android.content.Intent;
 import android.content.PermissionChecker;
 import android.content.pm.PackageManagerInternal;
-import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.Bundle;
@@ -192,7 +189,6 @@
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -246,8 +242,6 @@
     @Mock
     private PackageManagerInternal mPackageManagerInternal;
     @Mock
-    private RoleManager mRoleManager;
-    @Mock
     private AppStateTrackerImpl mAppStateTracker;
     @Mock
     private AlarmManagerService.ClockReceiver mClockReceiver;
@@ -393,11 +387,6 @@
         }
 
         @Override
-        void registerContentObserver(ContentObserver observer, Uri uri) {
-            // Do nothing.
-        }
-
-        @Override
         void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) {
             // Do nothing.
             // The tests become flaky with an error message of
@@ -484,10 +473,12 @@
         doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
                 () -> PermissionChecker.checkPermissionForPreflight(any(),
                         eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), anyInt(), anyString()));
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                () -> PermissionChecker.checkPermissionForPreflight(any(), eq(SCHEDULE_EXACT_ALARM),
+                        anyInt(), anyInt(), anyString()));
 
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
         when(mMockContext.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager);
-        when(mMockContext.getSystemService(RoleManager.class)).thenReturn(mRoleManager);
 
         registerAppIds(new String[]{TEST_CALLING_PACKAGE},
                 new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)});
@@ -2181,40 +2172,6 @@
         }
     }
 
-    @Test
-    public void hasScheduleExactAlarmBinderCallNotDenyListed() throws RemoteException {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        mockScheduleExactAlarmState(true, false, MODE_DEFAULT);
-        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
-        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmState(true, false, MODE_IGNORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-    }
-
-    @Test
-    public void hasScheduleExactAlarmBinderCallDenyListed() throws RemoteException {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        mockScheduleExactAlarmState(true, true, MODE_ERRORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmState(true, true, MODE_DEFAULT);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmState(true, true, MODE_IGNORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmState(true, true, MODE_ALLOWED);
-        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-    }
-
     private void mockChangeEnabled(long changeId, boolean enabled) {
         doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
                 any(UserHandle.class)));
@@ -2222,16 +2179,62 @@
     }
 
     @Test
-    public void hasScheduleExactAlarmBinderCallNotDeclared() throws RemoteException {
+    public void hasScheduleExactAlarmBinderCall() throws RemoteException {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
+
+        mockScheduleExactAlarmState(true);
+        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+        mockScheduleExactAlarmState(false);
+        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+    }
+
+    @Test
+    public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmState(false, false, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
+        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmState(false, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED);
+        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+    }
+
+    @Test
+    public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+
+        mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmState(false, true, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+        mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED);
+        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
+        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+    }
+
+    @Test
+    public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+
+        mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT);
+        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
+
+        mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
     }
 
@@ -2240,61 +2243,94 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         // canScheduleExactAlarms should be true regardless of any permission state.
-        mockUseExactAlarmState(true);
+        // Both SEA and UEA are denied in setUp.
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         mockUseExactAlarmState(false);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
-        mockScheduleExactAlarmState(false, true, MODE_DEFAULT);
-        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmState(false);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
     }
 
     @Test
-    public void canScheduleExactAlarmsBinderCall() throws RemoteException {
+    public void canScheduleExactAlarmsBinderCallPreT() throws RemoteException {
         // Policy permission is denied in setUp().
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         // No permission, no exemption.
-        mockScheduleExactAlarmState(true, true, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
         assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, no exemption.
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // Policy permission only, no exemption.
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         mockUseExactAlarmState(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         mockUseExactAlarmState(false);
 
         // User permission only, no exemption.
-        mockScheduleExactAlarmState(true, false, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // User permission only, no exemption.
-        mockScheduleExactAlarmState(true, true, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, exemption.
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, exemption.
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
         doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // Both permissions and exemption.
-        mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockUseExactAlarmState(true);
+        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+    }
+
+    @Test
+    public void canScheduleExactAlarmsBinderCall() throws RemoteException {
+        // Both permissions are denied in setUp().
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+        mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
+
+        // No permission, no exemption.
+        assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+        // Policy permission only, no exemption.
+        mockUseExactAlarmState(true);
+        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+        mockUseExactAlarmState(false);
+
+        // User permission only, no exemption.
+        mockScheduleExactAlarmState(true);
+        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+        // No permission, exemption.
+        mockScheduleExactAlarmState(false);
+        when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
+        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+        // No permission, core uid exemption.
+        when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
+        doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
+        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
+
+        // Both permissions and core uid exemption.
+        mockScheduleExactAlarmState(true);
         mockUseExactAlarmState(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
     }
@@ -2404,8 +2440,9 @@
     @Test
     public void alarmClockBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
 
-        mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmState(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
         final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
@@ -2431,9 +2468,10 @@
     public void alarmClockBinderCallWithUEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+        mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+        mockScheduleExactAlarmState(false);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
         final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
@@ -2455,7 +2493,7 @@
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
-    private void mockScheduleExactAlarmState(boolean declared, boolean denyList, int mode) {
+    private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) {
         String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
         when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
                 .thenReturn(requesters);
@@ -2470,6 +2508,20 @@
                 TEST_CALLING_PACKAGE)).thenReturn(mode);
     }
 
+    private void mockScheduleExactAlarmState(boolean granted) {
+        String[] requesters = granted ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
+        when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
+                .thenReturn(requesters);
+        mService.refreshExactAlarmCandidates();
+
+        final int result = granted ? PermissionChecker.PERMISSION_GRANTED
+                : PermissionChecker.PERMISSION_HARD_DENIED;
+        doReturn(result).when(
+                () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext),
+                        eq(SCHEDULE_EXACT_ALARM), anyInt(), eq(TEST_CALLING_UID),
+                        eq(TEST_CALLING_PACKAGE)));
+    }
+
     private void mockUseExactAlarmState(boolean granted) {
         final int result = granted ? PermissionChecker.PERMISSION_GRANTED
                 : PermissionChecker.PERMISSION_HARD_DENIED;
@@ -2483,7 +2535,7 @@
     public void alarmClockBinderCallWithoutPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2504,8 +2556,9 @@
     @Test
     public void exactBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
 
-        mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmState(true);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 0, alarmPi, null, null, null, null);
@@ -2529,9 +2582,10 @@
     public void exactBinderCallWithUEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+        mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+        mockScheduleExactAlarmState(false);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 0, alarmPi, null, null, null, null);
@@ -2555,7 +2609,7 @@
     public void exactBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2575,7 +2629,7 @@
     public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2601,7 +2655,7 @@
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2625,7 +2679,7 @@
     public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2651,7 +2705,7 @@
     public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2701,7 +2755,7 @@
     public void binderCallWithUserAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
         when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true);
 
@@ -3026,7 +3080,7 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
         assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
@@ -3039,7 +3093,7 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
@@ -3052,7 +3106,7 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
-        mockScheduleExactAlarmState(true, true, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
@@ -3068,7 +3122,7 @@
         when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
-        mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -3328,7 +3382,7 @@
                 .putExtra(Intent.EXTRA_REPLACING, true);
 
         mockUseExactAlarmState(false);
-        mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3336,7 +3390,7 @@
         assertEquals(5, mService.mAlarmStore.size());
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3344,7 +3398,7 @@
         assertEquals(5, mService.mAlarmStore.size());
 
         mockUseExactAlarmState(false);
-        mockScheduleExactAlarmState(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3363,55 +3417,6 @@
     }
 
     @Test
-    public void isScheduleExactAlarmAllowedByDefault() {
-        final String package1 = "priv";
-        final String package2 = "signed";
-        final String package3 = "normal";
-        final String package4 = "wellbeing";
-        final int uid1 = 1294;
-        final int uid2 = 8321;
-        final int uid3 = 3412;
-        final int uid4 = 4591;
-
-        when(mPackageManagerInternal.isUidPrivileged(uid1)).thenReturn(true);
-        when(mPackageManagerInternal.isUidPrivileged(uid2)).thenReturn(false);
-        when(mPackageManagerInternal.isUidPrivileged(uid3)).thenReturn(false);
-        when(mPackageManagerInternal.isUidPrivileged(uid4)).thenReturn(false);
-
-        when(mPackageManagerInternal.isPlatformSigned(package1)).thenReturn(false);
-        when(mPackageManagerInternal.isPlatformSigned(package2)).thenReturn(true);
-        when(mPackageManagerInternal.isPlatformSigned(package3)).thenReturn(false);
-        when(mPackageManagerInternal.isPlatformSigned(package4)).thenReturn(false);
-
-        when(mRoleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING)).thenReturn(
-                Arrays.asList(package4));
-
-        mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
-        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{
-                package1,
-                package3,
-        });
-
-        // Deny listed packages will be false.
-        assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1));
-        assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2));
-        assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3));
-        assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4));
-
-        mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
-        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{
-                package1,
-                package3,
-        });
-
-        // Deny list doesn't matter now, only exemptions should be true.
-        assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1));
-        assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2));
-        assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3));
-        assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4));
-    }
-
-    @Test
     public void alarmScheduledAtomPushed() {
         for (int i = 0; i < 10; i++) {
             final PendingIntent pi = getNewMockPendingIntent();
@@ -3510,7 +3515,7 @@
     }
 
     @Test
-    public void hasUseExactAlarmPermission() {
+    public void hasUseExactAlarmInternal() {
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         mockUseExactAlarmState(true);
@@ -3521,7 +3526,7 @@
     }
 
     @Test
-    public void hasUseExactAlarmPermissionChangeDisabled() {
+    public void hasUseExactAlarmInternalChangeDisabled() {
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, false);
 
         mockUseExactAlarmState(true);
@@ -3532,6 +3537,49 @@
     }
 
     @Test
+    public void hasScheduleExactAlarmInternal() {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
+
+        mockScheduleExactAlarmState(false);
+        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+        mockScheduleExactAlarmState(true);
+        assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+    }
+
+    @Test
+    public void hasScheduleExactAlarmInternalPreT() {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
+
+        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+    }
+
+    @Test
+    public void hasScheduleExactAlarmInternalPreS() {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
+
+        mockScheduleExactAlarmState(true);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+        mockScheduleExactAlarmState(false);
+        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+    }
+
+    @Test
     public void temporaryQuotaReserve_hasQuota() {
         final int quotaToFill = 5;
         final String package1 = "package1";
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 8b420a3..e056417 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -50,6 +50,7 @@
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
+import android.app.job.JobWorkItem;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
@@ -91,6 +92,7 @@
 
 public class JobSchedulerServiceTest {
     private static final String TAG = JobSchedulerServiceTest.class.getSimpleName();
+    private static final int TEST_UID = 10123;
 
     private JobSchedulerService mService;
 
@@ -177,6 +179,9 @@
         if (mMockingSession != null) {
             mMockingSession.finishMocking();
         }
+        mService.cancelJobsForUid(TEST_UID, true,
+                JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN,
+                "test cleanup");
     }
 
     private Clock getAdvancedClock(Clock clock, long incrementMs) {
@@ -257,9 +262,9 @@
         ConnectivityController connectivityController = mService.getConnectivityController();
         spyOn(connectivityController);
         mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
-        mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
-        mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
-        mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
+        mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
+        mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
+        mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
 
         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -279,26 +284,26 @@
         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
         grantRunUserInitiatedJobsPermission(true); // With permission
-        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
         doReturn(ConnectivityController.UNKNOWN_TIME)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
-        doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS / 2)
+        doReturn(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS / 2)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
-        doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS * 2)
+        doReturn(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
         assertEquals(
-                (long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+                (long) (mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS
                         * 2 * mService.mConstants
-                                .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
+                                .RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
-        doReturn(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS * 2)
+        doReturn(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS * 2)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+        assertEquals(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
     }
 
@@ -320,7 +325,7 @@
                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
 
         grantRunUserInitiatedJobsPermission(true);
-        assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+        assertEquals(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
                 mService.getMaxJobExecutionTimeMs(jobUIDT));
         grantRunUserInitiatedJobsPermission(false);
         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
@@ -1170,7 +1175,7 @@
                     i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
             assertEquals("Got unexpected result for schedule #" + (i + 1),
                     expected,
-                    mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", ""));
+                    mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", ""));
         }
     }
 
@@ -1191,7 +1196,7 @@
         for (int i = 0; i < 500; ++i) {
             assertEquals("Got unexpected result for schedule #" + (i + 1),
                     JobScheduler.RESULT_SUCCESS,
-                    mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", ""));
+                    mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", ""));
         }
     }
 
@@ -1212,7 +1217,7 @@
         for (int i = 0; i < 500; ++i) {
             assertEquals("Got unexpected result for schedule #" + (i + 1),
                     JobScheduler.RESULT_SUCCESS,
-                    mService.scheduleAsPackage(job, null, 10123, "proxied.package", 0, "JSSTest",
+                    mService.scheduleAsPackage(job, null, TEST_UID, "proxied.package", 0, "JSSTest",
                             ""));
         }
     }
@@ -1236,11 +1241,63 @@
                     i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
             assertEquals("Got unexpected result for schedule #" + (i + 1),
                     expected,
-                    mService.scheduleAsPackage(job, null, 10123, job.getService().getPackageName(),
+                    mService.scheduleAsPackage(job, null, TEST_UID,
+                            job.getService().getPackageName(),
                             0, "JSSTest", ""));
         }
     }
 
+    /**
+     * Tests that the number of persisted JobWorkItems is capped.
+     */
+    @Test
+    public void testScheduleLimiting_JobWorkItems_Nonpersisted() {
+        mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500;
+        mService.mConstants.ENABLE_API_QUOTAS = false;
+        mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
+        mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+        mService.updateQuotaTracker();
+
+        final JobInfo job = createJobInfo().setPersisted(false).build();
+        final JobWorkItem item = new JobWorkItem.Builder().build();
+        for (int i = 0; i < 1000; ++i) {
+            assertEquals("Got unexpected result for schedule #" + (i + 1),
+                    JobScheduler.RESULT_SUCCESS,
+                    mService.scheduleAsPackage(job, item, TEST_UID,
+                            job.getService().getPackageName(),
+                            0, "JSSTest", ""));
+        }
+    }
+
+    /**
+     * Tests that the number of persisted JobWorkItems is capped.
+     */
+    @Test
+    public void testScheduleLimiting_JobWorkItems_Persisted() {
+        mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500;
+        mService.mConstants.ENABLE_API_QUOTAS = false;
+        mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
+        mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+        mService.updateQuotaTracker();
+
+        final JobInfo job = createJobInfo().setPersisted(true).build();
+        final JobWorkItem item = new JobWorkItem.Builder().build();
+        for (int i = 0; i < 500; ++i) {
+            assertEquals("Got unexpected result for schedule #" + (i + 1),
+                    JobScheduler.RESULT_SUCCESS,
+                    mService.scheduleAsPackage(job, item, TEST_UID,
+                            job.getService().getPackageName(),
+                            0, "JSSTest", ""));
+        }
+        try {
+            mService.scheduleAsPackage(job, item, TEST_UID, job.getService().getPackageName(),
+                    0, "JSSTest", "");
+            fail("Added more items than allowed");
+        } catch (IllegalStateException expected) {
+            // Success
+        }
+    }
+
     /** Tests that jobs are removed from the pending list if the user stops the app. */
     @Test
     public void testUserStopRemovesPending() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
new file mode 100644
index 0000000..fd9dfe8
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.GnssMeasurementRequest;
+import android.location.IGnssMeasurementsListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementsProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private static final GnssConfiguration.HalInterfaceVersion HIDL_V2_1 =
+            new GnssConfiguration.HalInterfaceVersion(
+                    2, 1);
+    private static final GnssConfiguration.HalInterfaceVersion AIDL_V3 =
+            new GnssConfiguration.HalInterfaceVersion(
+                    GnssConfiguration.HalInterfaceVersion.AIDL_INTERFACE, 3);
+    private static final GnssMeasurementRequest ACTIVE_REQUEST =
+            new GnssMeasurementRequest.Builder().build();
+    private static final GnssMeasurementRequest PASSIVE_REQUEST =
+            new GnssMeasurementRequest.Builder().setIntervalMillis(
+                    GnssMeasurementRequest.PASSIVE_INTERVAL).build();
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
+    private @Mock IGnssMeasurementsListener mListener1;
+    private @Mock IGnssMeasurementsListener mListener2;
+    private @Mock IBinder mBinder1;
+    private @Mock IBinder mBinder2;
+
+    private GnssNative mGnssNative;
+
+    private GnssMeasurementsProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder1).when(mListener1).asBinder();
+        doReturn(mBinder2).when(mListener2).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(
+                GnssNative.create(injector, mMockConfiguration)));
+        mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
+        mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener_active() {
+        // add the active request
+        mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener1);
+        verify(mGnssNative, times(1)).startMeasurementCollection(
+                eq(ACTIVE_REQUEST.isFullTracking()),
+                eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()),
+                eq(ACTIVE_REQUEST.getIntervalMillis()));
+
+        // remove the active request
+        mTestProvider.removeListener(mListener1);
+        verify(mGnssNative, times(1)).stopMeasurementCollection();
+    }
+
+    @Test
+    public void testAddListener_passive() {
+        // add the passive request
+        mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1);
+        verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(),
+                anyInt());
+
+        // remove the passive request
+        mTestProvider.removeListener(mListener1);
+        verify(mGnssNative, times(1)).stopMeasurementCollection();
+    }
+
+    @Test
+    public void testReregister_aidlV3Plus() {
+        doReturn(AIDL_V3).when(mMockConfiguration).getHalInterfaceVersion();
+
+        // add the passive request
+        mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1);
+        verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(),
+                anyInt());
+
+        // add the active request, reregister with the active request
+        mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener2);
+        verify(mGnssNative, never()).stopMeasurementCollection();
+        verify(mGnssNative, times(1)).startMeasurementCollection(
+                eq(ACTIVE_REQUEST.isFullTracking()),
+                eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()),
+                eq(ACTIVE_REQUEST.getIntervalMillis()));
+
+        // remove the active request, reregister with the passive request
+        mTestProvider.removeListener(mListener2);
+        verify(mGnssNative, times(1)).stopMeasurementCollection();
+
+        // remove the passive request
+        mTestProvider.removeListener(mListener1);
+        verify(mGnssNative, times(2)).stopMeasurementCollection();
+    }
+
+    @Test
+    public void testReregister_preAidlV3() {
+        doReturn(HIDL_V2_1).when(mMockConfiguration).getHalInterfaceVersion();
+
+        // add the passive request
+        mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1);
+        verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(),
+                anyInt());
+
+        // add the active request, reregister with the active request
+        mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener2);
+        verify(mGnssNative, times(1)).stopMeasurementCollection();
+        verify(mGnssNative, times(1)).startMeasurementCollection(
+                eq(ACTIVE_REQUEST.isFullTracking()),
+                eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()),
+                eq(ACTIVE_REQUEST.getIntervalMillis()));
+
+        // remove the active request, reregister with the passive request
+        mTestProvider.removeListener(mListener2);
+        verify(mGnssNative, times(2)).stopMeasurementCollection();
+
+        // remove the passive request
+        mTestProvider.removeListener(mListener1);
+        verify(mGnssNative, times(3)).stopMeasurementCollection();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 71280ce..e81b63c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -403,6 +403,7 @@
         ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
         pkgInfo.applicationInfo = applicationInfo;
-        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), pkgInfo));
+        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), userId,
+                pkgInfo));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
index 8196d6a..e396263 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
@@ -52,7 +52,7 @@
 
     private static final int STATE_WITHOUT_NOTIFICATION = 1;
     private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2;
-    private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3;
+    private static final int STATE_WITH_ALL_NOTIFICATION = 3;
 
     private static final int VALID_APP_UID = 1000;
     private static final int INVALID_APP_UID = 2000;
@@ -68,6 +68,8 @@
     private static final String CONTENT_2 = "content2:%1$s";
     private static final String THERMAL_TITLE_2 = "thermal_title2";
     private static final String THERMAL_CONTENT_2 = "thermal_content2";
+    private static final String POWER_SAVE_TITLE_2 = "power_save_title2";
+    private static final String POWER_SAVE_CONTENT_2 = "power_save_content2";
 
     private DeviceStateNotificationController mController;
 
@@ -88,11 +90,12 @@
         notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION,
                 new DeviceStateNotificationController.NotificationInfo(
                         NAME_1, TITLE_1, CONTENT_1,
-                        "", ""));
-        notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION,
+                        "", "", "", ""));
+        notificationInfos.put(STATE_WITH_ALL_NOTIFICATION,
                 new DeviceStateNotificationController.NotificationInfo(
                         NAME_2, TITLE_2, CONTENT_2,
-                        THERMAL_TITLE_2, THERMAL_CONTENT_2));
+                        THERMAL_TITLE_2, THERMAL_CONTENT_2,
+                        POWER_SAVE_TITLE_2, POWER_SAVE_CONTENT_2));
 
         when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
         when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
@@ -139,10 +142,46 @@
     }
 
     @Test
+    public void test_powerSaveNotification() {
+        // Verify that the active notification is created.
+        mController.showStateActiveNotificationIfNeeded(
+                STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID);
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        Notification notification = mNotificationCaptor.getValue();
+        assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(String.format(CONTENT_2, VALID_APP_LABEL),
+                notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(Notification.FLAG_ONGOING_EVENT,
+                notification.flags & Notification.FLAG_ONGOING_EVENT);
+        Mockito.clearInvocations(mNotificationManager);
+
+        // Verify that the thermal critical notification is created.
+        mController.showPowerSaveNotificationIfNeeded(
+                STATE_WITH_ALL_NOTIFICATION);
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        notification = mNotificationCaptor.getValue();
+        assertEquals(POWER_SAVE_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(POWER_SAVE_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+        // Verify that the notification is canceled.
+        mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION);
+        verify(mNotificationManager).cancel(
+                DeviceStateNotificationController.NOTIFICATION_TAG,
+                DeviceStateNotificationController.NOTIFICATION_ID);
+    }
+
+    @Test
     public void test_thermalNotification() {
         // Verify that the active notification is created.
         mController.showStateActiveNotificationIfNeeded(
-                STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID);
+                STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID);
         verify(mNotificationManager).notify(
                 eq(DeviceStateNotificationController.NOTIFICATION_TAG),
                 eq(DeviceStateNotificationController.NOTIFICATION_ID),
@@ -157,7 +196,7 @@
 
         // Verify that the thermal critical notification is created.
         mController.showThermalCriticalNotificationIfNeeded(
-                STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION);
+                STATE_WITH_ALL_NOTIFICATION);
         verify(mNotificationManager).notify(
                 eq(DeviceStateNotificationController.NOTIFICATION_TAG),
                 eq(DeviceStateNotificationController.NOTIFICATION_ID),
@@ -168,7 +207,7 @@
         assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
 
         // Verify that the notification is canceled.
-        mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+        mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION);
         verify(mNotificationManager).cancel(
                 DeviceStateNotificationController.NOTIFICATION_TAG,
                 DeviceStateNotificationController.NOTIFICATION_ID);
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7125796..7e40f96 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -20,6 +20,8 @@
 import static android.content.Context.SENSOR_SERVICE;
 
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
 import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
@@ -327,7 +329,8 @@
                 + "        <name>THERMAL_TEST</name>\n"
                 + "        <flags>\n"
                 + "            <flag>FLAG_EMULATED_ONLY</flag>\n"
-                + "            <flag>FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
+                + "            <flag>FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
+                + "            <flag>FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE</flag>\n"
                 + "        </flags>\n"
                 + "    </device-state>\n"
                 + "</device-state-config>\n";
@@ -354,7 +357,8 @@
                         new DeviceState(3, "OPENED", 0 /* flags */),
                         new DeviceState(4, "THERMAL_TEST",
                                 DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
                 mDeviceStateArrayCaptor.getValue());
         // onStateChanged() should not be called because the provider has not yet been notified of
         // the initial sensor state.
@@ -419,7 +423,8 @@
                         new DeviceState(3, "OPENED", 0 /* flags */),
                         new DeviceState(4, "THERMAL_TEST",
                                 DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
                 mDeviceStateArrayCaptor.getValue());
         Mockito.clearInvocations(listener);
 
@@ -451,7 +456,65 @@
                         new DeviceState(3, "OPENED", 0 /* flags */),
                         new DeviceState(4, "THERMAL_TEST",
                                 DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                mDeviceStateArrayCaptor.getValue());
+    }
+
+    @Test
+    public void test_flagDisableWhenPowerSaveEnabled() throws Exception {
+        Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+        when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+        DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+        provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+        DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+        provider.setListener(listener);
+
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                mDeviceStateArrayCaptor.getValue());
+        Mockito.clearInvocations(listener);
+
+        provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        Mockito.clearInvocations(listener);
+
+        // The THERMAL_TEST state should be disabled due to power save being enabled.
+        provider.onPowerSaveModeChanged(true /* isPowerSaveModeEnabled */);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */) },
+                mDeviceStateArrayCaptor.getValue());
+        Mockito.clearInvocations(listener);
+
+        // The THERMAL_TEST state should be re-enabled.
+        provider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED));
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
                 mDeviceStateArrayCaptor.getValue());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index c73237e..0ae579b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -376,46 +376,6 @@
     }
 
     @Test
-    public void testGetAnimationTargets_windowsAreBeingReplaced() {
-        // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible)
-        //                                       +- [AppWindow1] (being-replaced)
-        //                   +- [Task2] - [ActivityRecord2] (closing, invisible)
-        //                                       +- [AppWindow2] (being-replaced)
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
-                TYPE_BASE_APPLICATION);
-        attrs.setTitle("AppWindow1");
-        final TestWindowState appWindow1 = createWindowState(attrs, activity1);
-        appWindow1.mWillReplaceWindow = true;
-        activity1.addWindow(appWindow1);
-
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(false);
-        attrs.setTitle("AppWindow2");
-        final TestWindowState appWindow2 = createWindowState(attrs, activity2);
-        appWindow2.mWillReplaceWindow = true;
-        activity2.addWindow(appWindow2);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // Animate opening apps even if it's already visible in case its windows are being replaced.
-        // Don't animate closing apps if it's already invisible even though its windows are being
-        // replaced.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
     public void testGetAnimationTargets_openingClosingInDifferentTask() {
         // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
         //                   |           +- [ActivityRecord2] (invisible)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 7d6cf4a..677ec46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -26,7 +26,10 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_OWN_FOCUS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -49,11 +52,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -67,12 +72,16 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.InputConfig;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
+import android.view.IWindow;
 import android.view.IWindowSessionCallback;
+import android.view.InputChannel;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.Surface;
@@ -690,6 +699,102 @@
         assertEquals(validRect, resultingArgs.mSourceCrop);
     }
 
+    @Test
+    public void testGrantInputChannel_sanitizeSpyWindowForApplications() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.FIRST_APPLICATION_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IWindow window = mock(IWindow.class);
+        final IBinder windowToken = mock(IBinder.class);
+        when(window.asBinder()).thenReturn(windowToken);
+        final IBinder focusGrantToken = mock(IBinder.class);
+
+        final InputChannel inputChannel = new InputChannel();
+        assertThrows(IllegalArgumentException.class, () ->
+                mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY,
+                        surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE,
+                        PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY, TYPE_APPLICATION,
+                        null /* windowToken */, focusGrantToken, "TestInputChannel",
+                        inputChannel));
+    }
+
+    @Test
+    public void testGrantInputChannel_allowSpyWindowForInputMonitorPermission() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.SYSTEM_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IWindow window = mock(IWindow.class);
+        final IBinder windowToken = mock(IBinder.class);
+        when(window.asBinder()).thenReturn(windowToken);
+        final IBinder focusGrantToken = mock(IBinder.class);
+
+        final InputChannel inputChannel = new InputChannel();
+        mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+                window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
+                INPUT_FEATURE_SPY, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                "TestInputChannel", inputChannel);
+
+        verify(mTransaction).setInputWindowInfo(
+                eq(surfaceControl),
+                argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
+    }
+
+    @Test
+    public void testUpdateInputChannel_sanitizeSpyWindowForApplications() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.FIRST_APPLICATION_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IWindow window = mock(IWindow.class);
+        final IBinder windowToken = mock(IBinder.class);
+        when(window.asBinder()).thenReturn(windowToken);
+        final IBinder focusGrantToken = mock(IBinder.class);
+
+        final InputChannel inputChannel = new InputChannel();
+        mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+                window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
+                0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                "TestInputChannel", inputChannel);
+        verify(mTransaction).setInputWindowInfo(
+                eq(surfaceControl),
+                argThat(h -> (h.inputConfig & InputConfig.SPY) == 0));
+
+        assertThrows(IllegalArgumentException.class, () ->
+                mWm.updateInputChannel(inputChannel.getToken(), DEFAULT_DISPLAY, surfaceControl,
+                        FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY,
+                        null /* region */));
+    }
+
+    @Test
+    public void testUpdateInputChannel_allowSpyWindowForInputMonitorPermission() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.SYSTEM_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IWindow window = mock(IWindow.class);
+        final IBinder windowToken = mock(IBinder.class);
+        when(window.asBinder()).thenReturn(windowToken);
+        final IBinder focusGrantToken = mock(IBinder.class);
+
+        final InputChannel inputChannel = new InputChannel();
+        mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+                window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
+                0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                "TestInputChannel", inputChannel);
+        verify(mTransaction).setInputWindowInfo(
+                eq(surfaceControl),
+                argThat(h -> (h.inputConfig & InputConfig.SPY) == 0));
+
+        mWm.updateInputChannel(inputChannel.getToken(), DEFAULT_DISPLAY, surfaceControl,
+                FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY,
+                null /* region */);
+        verify(mTransaction).setInputWindowInfo(
+                eq(surfaceControl),
+                argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
+    }
+
     private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index 2d382a2..79046db 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -37,6 +37,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
@@ -160,11 +161,20 @@
                 return null;
             }
             final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
-            if (!isServiceAvailableForUser(serviceComponent)) {
+            boolean isServiceAvailableForUser;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                isServiceAvailableForUser = isServiceAvailableForUser(serviceComponent);
                 if (mMaster.verbose) {
-                    Slog.v(TAG, "ensureRemoteServiceLocked(): " + serviceComponent
-                            + " is not available,");
+                    Slog.v(TAG, "ensureRemoteServiceLocked(): isServiceAvailableForUser="
+                            + isServiceAvailableForUser);
                 }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            if (!isServiceAvailableForUser) {
+                Slog.w(TAG, "ensureRemoteServiceLocked(): " + serviceComponent
+                        + " is not available,");
                 return null;
             }
             mRemoteTranslationService = new RemoteTranslationService(getContext(), serviceComponent,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 06fc416..dbc824c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -68,6 +68,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
+import android.service.voice.DetectorFailure;
 import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionService;
 import android.service.voice.HotwordDetectionServiceFailure;
@@ -623,11 +624,9 @@
         mRemoteDetectionService = remoteDetectionService;
     }
 
-    void reportErrorLocked(int errorCode, @NonNull String errorMessage) {
+    void reportErrorLocked(@NonNull DetectorFailure detectorFailure) {
         try {
-            // TODO: Use instanceof(this) to get different detector to set the right error source.
-            mCallback.onDetectionFailure(
-                    new HotwordDetectionServiceFailure(errorCode, errorMessage));
+            mCallback.onDetectionFailure(detectorFailure);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to report onError status: " + e);
             if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 025e1dc..4fd5979 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -51,11 +51,14 @@
 import android.os.SharedMemory;
 import android.provider.DeviceConfig;
 import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordDetector;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.ISandboxedDetectionService;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.UnknownFailure;
 import android.service.voice.VisualQueryDetectionService;
+import android.service.voice.VisualQueryDetectionServiceFailure;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
 import android.speech.IRecognitionServiceManager;
 import android.util.Slog;
@@ -109,6 +112,16 @@
     private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
     private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
 
+    /**
+     * Indicates the {@link HotwordDetectionService} is created.
+     */
+    private static final int DETECTION_SERVICE_TYPE_HOTWORD = 1;
+
+    /**
+     * Indicates the {@link VisualQueryDetectionService} is created.
+     */
+    private static final int DETECTION_SERVICE_TYPE_VISUAL_QUERY = 2;
+
     // TODO: This may need to be a Handler(looper)
     private final ScheduledExecutorService mScheduledExecutorService =
             Executors.newSingleThreadScheduledExecutor();
@@ -186,11 +199,11 @@
 
         mHotwordDetectionServiceConnectionFactory =
                 new ServiceConnectionFactory(hotwordDetectionServiceIntent,
-                        bindInstantServiceAllowed);
+                        bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_HOTWORD);
 
         mVisualQueryDetectionServiceConnectionFactory =
                 new ServiceConnectionFactory(visualQueryDetectionServiceIntent,
-                        bindInstantServiceAllowed);
+                        bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_VISUAL_QUERY);
 
 
         mLastRestartInstant = Instant.now();
@@ -604,17 +617,20 @@
     private class ServiceConnectionFactory {
         private final Intent mIntent;
         private final int mBindingFlags;
+        private final int mDetectionServiceType;
 
-        ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed) {
+        ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed,
+                int detectionServiceType) {
             mIntent = intent;
             mBindingFlags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
+            mDetectionServiceType = detectionServiceType;
         }
 
         ServiceConnection createLocked() {
             ServiceConnection connection =
                     new ServiceConnection(mContext, mIntent, mBindingFlags, mUser,
                             ISandboxedDetectionService.Stub::asInterface,
-                            mRestartCount % MAX_ISOLATED_PROCESS_NUMBER);
+                            mRestartCount % MAX_ISOLATED_PROCESS_NUMBER, mDetectionServiceType);
             connection.connect();
 
             updateAudioFlinger(connection, mAudioFlinger);
@@ -635,15 +651,17 @@
         private boolean mRespectServiceConnectionStatusChanged = true;
         private boolean mIsBound = false;
         private boolean mIsLoggedFirstConnect = false;
+        private final int mDetectionServiceType;
 
         ServiceConnection(@NonNull Context context,
                 @NonNull Intent serviceIntent, int bindingFlags, int userId,
                 @Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface,
-                int instanceNumber) {
+                int instanceNumber, int detectionServiceType) {
             super(context, serviceIntent, bindingFlags, userId, binderAsInterface);
             this.mIntent = serviceIntent;
             this.mBindingFlags = bindingFlags;
             this.mInstanceNumber = instanceNumber;
+            this.mDetectionServiceType = detectionServiceType;
         }
 
         @Override // from ServiceConnector.Impl
@@ -660,14 +678,14 @@
                 mIsBound = connected;
 
                 if (!connected) {
-                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                                 HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
                                 mVoiceInteractionServiceUid);
                     }
                 } else if (!mIsLoggedFirstConnect) {
                     mIsLoggedFirstConnect = true;
-                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                                 HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
                                 mVoiceInteractionServiceUid);
@@ -684,7 +702,7 @@
         @Override
         public void binderDied() {
             super.binderDied();
-            Slog.w(TAG, "binderDied");
+            Slog.w(TAG, "binderDied mDetectionServiceType = " + mDetectionServiceType);
             synchronized (mLock) {
                 if (!mRespectServiceConnectionStatusChanged) {
                     Slog.v(TAG, "Ignored #binderDied event");
@@ -693,13 +711,10 @@
             }
             //TODO(b265535257): report error to either service only.
             synchronized (HotwordDetectionConnection.this.mLock) {
-                runForEachDetectorSessionLocked((session) -> {
-                    session.reportErrorLocked(DetectorSession.HOTWORD_DETECTION_SERVICE_DIED,
-                            "Detection service is dead.");
-                });
+                runForEachDetectorSessionLocked(this::reportBinderDiedLocked);
             }
             // Can improve to log exit reason if needed
-            if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+            if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         mDetectorType,
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
@@ -711,7 +726,7 @@
         protected boolean bindService(
                 @NonNull android.content.ServiceConnection serviceConnection) {
             try {
-                if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                     HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                             HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
                             mVoiceInteractionServiceUid);
@@ -723,7 +738,12 @@
                         mExecutor,
                         serviceConnection);
                 if (!bindResult) {
-                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    Slog.w(TAG,
+                            "bindService failure mDetectionServiceType = " + mDetectionServiceType);
+                    synchronized (HotwordDetectionConnection.this.mLock) {
+                        runForEachDetectorSessionLocked(this::reportBindServiceFailureLocked);
+                    }
+                    if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                                 HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
                                 mVoiceInteractionServiceUid);
@@ -731,7 +751,7 @@
                 }
                 return bindResult;
             } catch (IllegalArgumentException e) {
-                if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                     HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                             HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
                             mVoiceInteractionServiceUid);
@@ -752,6 +772,42 @@
                 mRespectServiceConnectionStatusChanged = false;
             }
         }
+
+        private void reportBinderDiedLocked(DetectorSession detectorSession) {
+            if (mDetectionServiceType == DETECTION_SERVICE_TYPE_HOTWORD && (
+                    detectorSession instanceof DspTrustedHotwordDetectorSession
+                            || detectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
+                detectorSession.reportErrorLocked(new HotwordDetectionServiceFailure(
+                        HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED,
+                        "Detection service is dead."));
+            } else if (mDetectionServiceType == DETECTION_SERVICE_TYPE_VISUAL_QUERY
+                    && detectorSession instanceof VisualQueryDetectorSession) {
+                detectorSession.reportErrorLocked(new VisualQueryDetectionServiceFailure(
+                        VisualQueryDetectionServiceFailure.ERROR_CODE_BINDING_DIED,
+                        "Detection service is dead."));
+            } else {
+                detectorSession.reportErrorLocked(new UnknownFailure(
+                        "Detection service is dead with unknown detection service type."));
+            }
+        }
+
+        private void reportBindServiceFailureLocked(DetectorSession detectorSession) {
+            if (mDetectionServiceType == DETECTION_SERVICE_TYPE_HOTWORD && (
+                    detectorSession instanceof DspTrustedHotwordDetectorSession
+                            || detectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
+                detectorSession.reportErrorLocked(new HotwordDetectionServiceFailure(
+                        HotwordDetectionServiceFailure.ERROR_CODE_BIND_FAILURE,
+                        "Bind detection service failure."));
+            } else if (mDetectionServiceType == DETECTION_SERVICE_TYPE_VISUAL_QUERY
+                    && detectorSession instanceof VisualQueryDetectorSession) {
+                detectorSession.reportErrorLocked(new VisualQueryDetectionServiceFailure(
+                        VisualQueryDetectionServiceFailure.ERROR_CODE_BIND_FAILURE,
+                        "Bind detection service failure."));
+            } else {
+                detectorSession.reportErrorLocked(new UnknownFailure(
+                        "Bind detection service failure with unknown detection service type."));
+            }
+        }
     }
 
     @SuppressWarnings("GuardedBy")
diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index 2442083..a81444d 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -24,15 +24,22 @@
  */
 oneway interface ISatelliteTransmissionUpdateCallback {
     /**
-     * Called when satellite datagram transfer state changed.
+     * Called when satellite datagram send state changed.
      *
-     * @param state The new datagram transfer state.
+     * @param state The new send datagram transfer state.
      * @param sendPendingCount The number of datagrams that are currently being sent.
-     * @param receivePendingCount The number of datagrams that are currently being received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    void onDatagramTransferStateChanged(in int state, in int sendPendingCount,
-            in int receivePendingCount, in int errorCode);
+    void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode);
+
+    /**
+     * Called when satellite datagram receive state changed.
+     *
+     * @param state The new receive datagram transfer state.
+     * @param receivePendingCount The number of datagrams that are currently pending to be received.
+     * @param errorCode If datagram transfer failed, the reason for failure.
+     */
+    void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode);
 
     /**
      * Called when the satellite position changed.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index d0abfbf..e32566d 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -647,6 +647,7 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteDatagramTransferState {}
+    // TODO: Split into two enums for sending and receiving states
 
     /**
      * Satellite modem is in idle state.
@@ -750,20 +751,28 @@
                 };
                 ISatelliteTransmissionUpdateCallback internalCallback =
                         new ISatelliteTransmissionUpdateCallback.Stub() {
-                            @Override
-                            public void onDatagramTransferStateChanged(int state,
-                                    int sendPendingCount, int receivePendingCount, int errorCode) {
-                                executor.execute(() -> Binder.withCleanCallingIdentity(
-                                        () -> callback.onDatagramTransferStateChanged(
-                                                state, sendPendingCount, receivePendingCount,
-                                                errorCode)));
-                            }
 
                             @Override
                             public void onSatellitePositionChanged(PointingInfo pointingInfo) {
                                 executor.execute(() -> Binder.withCleanCallingIdentity(
                                         () -> callback.onSatellitePositionChanged(pointingInfo)));
                             }
+
+                            @Override
+                            public void onSendDatagramStateChanged(int state, int sendPendingCount,
+                                    int errorCode) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSendDatagramStateChanged(
+                                                state, sendPendingCount, errorCode)));
+                            }
+
+                            @Override
+                            public void onReceiveDatagramStateChanged(int state,
+                                    int receivePendingCount, int errorCode) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onReceiveDatagramStateChanged(
+                                                state, receivePendingCount, errorCode)));
+                            }
                         };
                 sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
                 telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index 0efbd1f..d4fe57a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -33,14 +33,24 @@
     void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
 
     /**
-     * Called when satellite datagram transfer state changed.
+     * Called when satellite datagram send state changed.
      *
-     * @param state The new datagram transfer state.
+     * @param state The new send datagram transfer state.
      * @param sendPendingCount The number of datagrams that are currently being sent.
-     * @param receivePendingCount The number of datagrams that are currently being received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    void onDatagramTransferStateChanged(@SatelliteManager.SatelliteDatagramTransferState int state,
-            int sendPendingCount, int receivePendingCount,
+    void onSendDatagramStateChanged(
+            @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
+            @SatelliteManager.SatelliteError int errorCode);
+
+    /**
+     * Called when satellite datagram receive state changed.
+     *
+     * @param state The new receive datagram transfer state.
+     * @param receivePendingCount The number of datagrams that are currently pending to be received.
+     * @param errorCode If datagram transfer failed, the reason for failure.
+     */
+    void onReceiveDatagramStateChanged(
+            @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
             @SatelliteManager.SatelliteError int errorCode);
 }
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
index d2a6bf2..81efda1 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static org.junit.Assume.assumeFalse;
+
 import android.app.AlarmManager;
 import android.content.Context;
 import android.os.Environment;
@@ -112,6 +114,7 @@
 
     @Before
     public void setUp() throws IOException {
+        assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false));
         File dataDir = getContext().getDataDir();
         mBigFile = new File(dataDir, BIG_FILE);
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
new file mode 100644
index 0000000..3289bc6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppAfterCameraTestCfArm(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
new file mode 100644
index 0000000..4aa78d4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+class OpenAppFromNotificationColdCfArm(flicker: FlickerTest) :
+    OpenAppFromNotificationCold(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
new file mode 100644
index 0000000..9679059
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppWarmTestCfArm(flicker: FlickerTest) : OpenAppWarmTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 83893ba..a4c48fd 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -13,6 +13,9 @@
         "src/**/*.java",
         "src/**/*.kt",
     ],
+    kotlincflags: [
+        "-Werror",
+    ],
     platform_apis: true,
     certificate: "platform",
     static_libs: [
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 1d65cc3..0246426 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -73,7 +73,7 @@
         val contentResolver = instrumentation.targetContext.contentResolver
         hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
         Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
-        PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName()
+        PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName()
     }
 
     @After
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
index d83a457..3a24406 100644
--- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -45,7 +45,8 @@
     private lateinit var mInputMonitor: InputMonitor
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        mInputMonitor = InputManager.getInstance().monitorGestureInput(MONITOR_NAME, displayId)
+        val inputManager = getSystemService(InputManager::class.java)
+        mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId)
         mInputEventReceiver = UnresponsiveReceiver(
                 mInputMonitor.getInputChannel(), Looper.myLooper())
     }
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index f183a9b..b7f5b15 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -20,7 +20,6 @@
 import android.service.voice.AlwaysOnHotwordDetector;
 import android.service.voice.AlwaysOnHotwordDetector.Callback;
 import android.service.voice.AlwaysOnHotwordDetector.EventPayload;
-import android.service.voice.HotwordDetector;
 import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 
@@ -84,24 +83,16 @@
                 break;
             case AlwaysOnHotwordDetector.STATE_KEYPHRASE_UNENROLLED:
                 Log.i(TAG, "STATE_KEYPHRASE_UNENROLLED");
-                try {
-                    Intent enroll = mHotwordDetector.createEnrollIntent();
-                    Log.i(TAG, "Need to enroll with " + enroll);
-                } catch (HotwordDetector.IllegalDetectorStateException e) {
-                    Log.e(TAG, "createEnrollIntent failed", e);
-                }
+                Intent enroll = mHotwordDetector.createEnrollIntent();
+                Log.i(TAG, "Need to enroll with " + enroll);
                 break;
             case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED:
                 Log.i(TAG, "STATE_KEYPHRASE_ENROLLED - starting recognition");
-                try {
-                    if (mHotwordDetector.startRecognition(
-                            AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
-                        Log.i(TAG, "startRecognition succeeded");
-                    } else {
-                        Log.i(TAG, "startRecognition failed");
-                    }
-                } catch (HotwordDetector.IllegalDetectorStateException e) {
-                    Log.e(TAG, "startRecognition failed", e);
+                if (mHotwordDetector.startRecognition(
+                        AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
+                    Log.i(TAG, "startRecognition succeeded");
+                } else {
+                    Log.i(TAG, "startRecognition failed");
                 }
                 break;
         }
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 5fdfb66..2ce2167 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -209,6 +209,8 @@
     AddOptionalFlag("--compile-sdk-version-name",
         "Version name to inject into the AndroidManifest.xml if none is present.",
         &options_.manifest_fixer_options.compile_sdk_version_codename);
+    AddOptionalFlagList("--fingerprint-prefix", "Fingerprint prefix to add to install constraints.",
+                        &options_.manifest_fixer_options.fingerprint_prefixes);
     AddOptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
         &shared_lib_);
     AddOptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib_);
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index c66f4e5..a43bf1b 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -2120,6 +2120,33 @@
   }
 };
 
+/** Represents <install-constraints> elements. **/
+class InstallConstraints : public ManifestExtractor::Element {
+ public:
+  InstallConstraints() = default;
+  std::vector<std::string> fingerprint_prefixes;
+
+  void Extract(xml::Element* element) override {
+    for (xml::Element* child : element->GetChildElements()) {
+      if (child->name == "fingerprint-prefix") {
+        xml::Attribute* attr = child->FindAttribute(kAndroidNamespace, "value");
+        if (attr) {
+          fingerprint_prefixes.push_back(attr->value);
+        }
+      }
+    }
+  }
+
+  void Print(text::Printer* printer) override {
+    if (!fingerprint_prefixes.empty()) {
+      printer->Print(StringPrintf("install-constraints:\n"));
+      for (const auto& prefix : fingerprint_prefixes) {
+        printer->Print(StringPrintf("  fingerprint-prefix='%s'\n", prefix.c_str()));
+      }
+    }
+  }
+};
+
 /** Represents <original-package> elements. **/
 class OriginalPackage : public ManifestExtractor::Element {
  public:
@@ -2869,7 +2896,7 @@
 constexpr const char* GetExpectedTagForType() {
   // This array does not appear at runtime, as GetExpectedTagForType function is used by compiler
   // to inject proper 'expected_tag' into ElementCast.
-  std::array<std::pair<const char*, bool>, 37> tags = {
+  std::array<std::pair<const char*, bool>, 38> tags = {
       std::make_pair("action", std::is_same<Action, T>::value),
       std::make_pair("activity", std::is_same<Activity, T>::value),
       std::make_pair("additional-certificate", std::is_same<AdditionalCertificate, T>::value),
@@ -2878,6 +2905,7 @@
       std::make_pair("compatible-screens", std::is_same<CompatibleScreens, T>::value),
       std::make_pair("feature-group", std::is_same<FeatureGroup, T>::value),
       std::make_pair("input-type", std::is_same<InputType, T>::value),
+      std::make_pair("install-constraints", std::is_same<InstallConstraints, T>::value),
       std::make_pair("intent-filter", std::is_same<IntentFilter, T>::value),
       std::make_pair("meta-data", std::is_same<MetaData, T>::value),
       std::make_pair("manifest", std::is_same<Manifest, T>::value),
@@ -2948,6 +2976,7 @@
           {"compatible-screens", &CreateType<CompatibleScreens>},
           {"feature-group", &CreateType<FeatureGroup>},
           {"input-type", &CreateType<InputType>},
+          {"install-constraints", &CreateType<InstallConstraints>},
           {"intent-filter", &CreateType<IntentFilter>},
           {"manifest", &CreateType<Manifest>},
           {"meta-data", &CreateType<MetaData>},
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 56d9075..53f0abe 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -749,6 +749,23 @@
     attr->value = options_.compile_sdk_version_codename.value();
   }
 
+  if (!options_.fingerprint_prefixes.empty()) {
+    xml::Element* install_constraints_el = root->FindChild({}, "install-constraints");
+    if (install_constraints_el == nullptr) {
+      std::unique_ptr<xml::Element> install_constraints = std::make_unique<xml::Element>();
+      install_constraints->name = "install-constraints";
+      install_constraints_el = install_constraints.get();
+      root->AppendChild(std::move(install_constraints));
+    }
+    for (const std::string& prefix : options_.fingerprint_prefixes) {
+      std::unique_ptr<xml::Element> prefix_el = std::make_unique<xml::Element>();
+      prefix_el->name = "fingerprint-prefix";
+      xml::Attribute* attr = prefix_el->FindOrCreateAttribute(xml::kSchemaAndroid, "value");
+      attr->value = prefix;
+      install_constraints_el->AppendChild(std::move(prefix_el));
+    }
+  }
+
   xml::XmlActionExecutor executor;
   if (!BuildRules(&executor, context->GetDiagnostics())) {
     return false;
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 90f5177..175ab6f 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -18,11 +18,10 @@
 #define AAPT_LINK_MANIFESTFIXER_H
 
 #include <string>
+#include <vector>
 
 #include "android-base/macros.h"
-
 #include "process/IResourceTableConsumer.h"
-
 #include "xml/XmlActionExecutor.h"
 #include "xml/XmlDom.h"
 
@@ -75,6 +74,9 @@
   // 'android:compileSdkVersionCodename' in the <manifest> tag.
   std::optional<std::string> compile_sdk_version_codename;
 
+  // The fingerprint prefixes to be added to the <install-constraints> tag.
+  std::vector<std::string> fingerprint_prefixes;
+
   // Whether validation errors should be treated only as warnings. If this is 'true', then an
   // incorrect node will not result in an error, but only as a warning, and the parsing will
   // continue.
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 7180ae6..1b8f05b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -965,6 +965,63 @@
   ASSERT_THAT(manifest, IsNull());
 }
 
+TEST_F(ManifestFixerTest, InsertFingerprintPrefixIfNotExist) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+      </manifest>)";
+  ManifestFixerOptions options;
+  options.fingerprint_prefixes = {"foo", "bar"};
+
+  std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, NotNull());
+  xml::Element* install_constraints = manifest->root.get()->FindChild({}, "install-constraints");
+  ASSERT_THAT(install_constraints, NotNull());
+  std::vector<xml::Element*> fingerprint_prefixes = install_constraints->GetChildElements();
+  EXPECT_EQ(fingerprint_prefixes.size(), 2);
+  xml::Attribute* attr;
+  EXPECT_THAT(fingerprint_prefixes[0]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[0]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("foo"));
+  EXPECT_THAT(fingerprint_prefixes[1]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[1]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("bar"));
+}
+
+TEST_F(ManifestFixerTest, AppendFingerprintPrefixIfExists) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+          <install-constraints>
+            <fingerprint-prefix android:value="foo" />
+          </install-constraints>
+      </manifest>)";
+  ManifestFixerOptions options;
+  options.fingerprint_prefixes = {"bar", "baz"};
+
+  std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, NotNull());
+  xml::Element* install_constraints = manifest->root.get()->FindChild({}, "install-constraints");
+  ASSERT_THAT(install_constraints, NotNull());
+  std::vector<xml::Element*> fingerprint_prefixes = install_constraints->GetChildElements();
+  EXPECT_EQ(fingerprint_prefixes.size(), 3);
+  xml::Attribute* attr;
+  EXPECT_THAT(fingerprint_prefixes[0]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[0]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("foo"));
+  EXPECT_THAT(fingerprint_prefixes[1]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[1]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("bar"));
+  EXPECT_THAT(fingerprint_prefixes[2]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[2]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("baz"));
+}
+
 TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) {
   std::string input = R"(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 423a684..b4f6a1c 100644
--- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -36,7 +36,6 @@
         CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
-        RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG,
         PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
         PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
     )
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
deleted file mode 100644
index c3e0428..0000000
--- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
+++ /dev/null
@@ -1,927 +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.google.android.lint
-
-import com.android.tools.lint.checks.DataFlowAnalyzer
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.ConstantEvaluator
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.android.tools.lint.detector.api.UastLintUtils.Companion.findLastAssignment
-import com.android.tools.lint.detector.api.getMethodName
-import com.intellij.psi.PsiMethod
-import com.intellij.psi.PsiVariable
-import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UParenthesizedExpression
-import org.jetbrains.uast.UQualifiedReferenceExpression
-import org.jetbrains.uast.getContainingUMethod
-import org.jetbrains.uast.isNullLiteral
-import org.jetbrains.uast.skipParenthesizedExprDown
-import org.jetbrains.uast.tryResolve
-
-/**
- * Detector that identifies `registerReceiver()` calls which are missing the `RECEIVER_EXPORTED` or
- * `RECEIVER_NOT_EXPORTED` flags on T+.
- *
- * TODO: Add API level conditions to better support non-platform code.
- * 1. Check if registerReceiver() call is reachable on T+.
- * 2. Check if targetSdkVersion is T+.
- *
- * eg: isWithinVersionCheckConditional(context, node, 31, false)
- * eg: isPrecededByVersionCheckExit(context, node, 31) ?
- */
-@Suppress("UnstableApiUsage")
-class RegisterReceiverFlagDetector : Detector(), SourceCodeScanner {
-
-    override fun getApplicableMethodNames(): List<String> = listOf(
-            "registerReceiver",
-            "registerReceiverAsUser",
-            "registerReceiverForAllUsers"
-    )
-
-    private fun checkIsProtectedReceiverAndReturnUnprotectedActions(
-            filterArg: UExpression,
-            node: UCallExpression,
-            evaluator: ConstantEvaluator
-        ): Pair<Boolean, List<String>> { // isProtected, unprotectedActions
-            val actions = mutableSetOf<String>()
-            val construction = findIntentFilterConstruction(filterArg, node)
-
-            if (construction == null) return Pair(false, listOf<String>())
-            val constructorActionArg = construction.getArgumentForParameter(0)
-            (constructorActionArg?.let(evaluator::evaluate) as? String)?.let(actions::add)
-
-            val actionCollectorVisitor =
-                ActionCollectorVisitor(setOf(construction), node, evaluator)
-
-            val parent = node.getContainingUMethod()
-            parent?.accept(actionCollectorVisitor)
-            actions.addAll(actionCollectorVisitor.actions)
-
-            // If we failed to evaluate any actions, there will be a null action in the set.
-            val isProtected =
-              actions.all(PROTECTED_BROADCASTS::contains) &&
-                !actionCollectorVisitor.intentFilterEscapesScope
-            val unprotectedActionsList = actions.filterNot(PROTECTED_BROADCASTS::contains)
-            return Pair(isProtected, unprotectedActionsList)
-        }
-
-    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        if (!context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) return
-
-        // The parameter positions vary across the various registerReceiver*() methods, so rather
-        // than hardcode them we simply look them up based on the parameter name and type.
-        val receiverArg =
-            findArgument(node, method, "android.content.BroadcastReceiver", "receiver")
-        val filterArg = findArgument(node, method, "android.content.IntentFilter", "filter")
-        val flagsArg = findArgument(node, method, "int", "flags")
-
-        if (receiverArg == null || receiverArg.isNullLiteral() || filterArg == null) {
-            return
-        }
-
-        val evaluator = ConstantEvaluator().allowFieldInitializers()
-
-        val (isProtected, unprotectedActionsList) =
-          checkIsProtectedReceiverAndReturnUnprotectedActions(filterArg, node, evaluator)
-
-        val flags = evaluator.evaluate(flagsArg) as? Int
-
-        if (!isProtected) {
-            val actionsList = unprotectedActionsList.joinToString(", ", "", "", -1, "")
-            val message = "$receiverArg is missing 'RECEIVED_EXPORTED` or 'RECEIVE_NOT_EXPORTED' " +
-                            "flag for unprotected broadcast(s) registered for $actionsList."
-            if (flagsArg == null) {
-                context.report(
-                  ISSUE_RECEIVER_EXPORTED_FLAG, node, context.getLocation(node), message)
-            } else if (flags != null && (flags and RECEIVER_EXPORTED_FLAG_PRESENT_MASK) == 0) {
-                context.report(
-                  ISSUE_RECEIVER_EXPORTED_FLAG, node, context.getLocation(flagsArg), message)
-            }
-        }
-
-        if (DEBUG) {
-            println(node.asRenderString())
-            println("Unprotected Actions: $unprotectedActionsList")
-            println("Protected: $isProtected")
-            println("Flags: $flags")
-        }
-    }
-
-    /** Finds the first argument of a method that matches the given parameter type and name. */
-    private fun findArgument(
-            node: UCallExpression,
-            method: PsiMethod,
-            type: String,
-            name: String
-    ): UExpression? {
-        val psiParameter = method.parameterList.parameters.firstOrNull {
-            it.type.canonicalText == type && it.name == name
-        } ?: return null
-        val argument = node.getArgumentForParameter(psiParameter.parameterIndex())
-        return argument?.skipParenthesizedExprDown()
-    }
-
-    /**
-     * For the supplied expression (eg. intent filter argument), attempts to find its construction.
-     * This will be an `IntentFilter()` constructor, an `IntentFilter.create()` call, or `null`.
-     */
-    private fun findIntentFilterConstruction(
-            expression: UExpression,
-            node: UCallExpression
-    ): UCallExpression? {
-        val resolved = expression.tryResolve()
-
-        if (resolved is PsiVariable) {
-            val assignment = findLastAssignment(resolved, node) ?: return null
-            return findIntentFilterConstruction(assignment, node)
-        }
-
-        if (expression is UParenthesizedExpression) {
-            return findIntentFilterConstruction(expression.expression, node)
-        }
-
-        if (expression is UQualifiedReferenceExpression) {
-            val call = expression.selector as? UCallExpression ?: return null
-            return if (isReturningContext(call)) {
-                // eg. filter.apply { addAction("abc") } --> use filter variable.
-                findIntentFilterConstruction(expression.receiver, node)
-            } else {
-                // eg. IntentFilter.create("abc") --> use create("abc") UCallExpression.
-                findIntentFilterConstruction(call, node)
-            }
-        }
-
-        val method = resolved as? PsiMethod ?: return null
-        return if (isIntentFilterFactoryMethod(method)) {
-            expression as? UCallExpression
-        } else {
-            null
-        }
-    }
-
-    private fun isIntentFilterFactoryMethod(method: PsiMethod) =
-            (method.containingClass?.qualifiedName == "android.content.IntentFilter" &&
-               (method.returnType?.canonicalText == "android.content.IntentFilter" ||
-                    method.isConstructor))
-
-    /**
-     * Returns true if the given call represents a Kotlin scope function where the return value is
-     * the context object; see https://kotlinlang.org/docs/scope-functions.html#function-selection.
-     */
-    private fun isReturningContext(node: UCallExpression): Boolean {
-        val name = getMethodName(node)
-        if (name == "apply" || name == "also") {
-            return isScopingFunction(node)
-        }
-        return false
-    }
-
-    /**
-     * Returns true if the given node appears to be one of the scope functions. Only checks parent
-     * class; caller should intend that it's actually one of let, with, apply, etc.
-     */
-    private fun isScopingFunction(node: UCallExpression): Boolean {
-        val called = node.resolve() ?: return true
-        // See libraries/stdlib/jvm/build/stdlib-declarations.json
-        return called.containingClass?.qualifiedName == "kotlin.StandardKt__StandardKt"
-    }
-
-    inner class ActionCollectorVisitor(
-        start: Collection<UElement>,
-        val functionCall: UCallExpression,
-        val evaluator: ConstantEvaluator,
-    ) : DataFlowAnalyzer(start) {
-       private var finished = false
-       var intentFilterEscapesScope = false; private set
-       val actions = mutableSetOf<String>()
-
-       override fun argument(call: UCallExpression, reference: UElement) {
-           // TODO: Remove this temporary fix for DataFlowAnalyzer bug (ag/15787550):
-           if (reference !in call.valueArguments) return
-           val methodNames = super@RegisterReceiverFlagDetector.getApplicableMethodNames()
-           when {
-               finished -> return
-               // We've reached the registerReceiver*() call in question.
-               call == functionCall -> finished = true
-               // The filter 'intentFilterEscapesScope' to a method which could modify it.
-               methodNames != null && getMethodName(call)!! !in methodNames ->
-                 intentFilterEscapesScope = true
-           }
-       }
-
-       // Fixed in b/199163915: DataFlowAnalyzer doesn't call this for Kotlin properties.
-       override fun field(field: UElement) {
-           if (!finished) intentFilterEscapesScope = true
-       }
-
-       override fun receiver(call: UCallExpression) {
-           if (!finished && getMethodName(call) == "addAction") {
-               val actionArg = call.getArgumentForParameter(0)
-               if (actionArg != null) {
-                   val action = evaluator.evaluate(actionArg) as? String
-                   if (action != null) actions.add(action)
-               }
-           }
-       }
-    }
-
-    companion object {
-        const val DEBUG = false
-
-        private const val RECEIVER_EXPORTED = 0x2
-        private const val RECEIVER_NOT_EXPORTED = 0x4
-        private const val RECEIVER_EXPORTED_FLAG_PRESENT_MASK =
-          RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED
-
-        @JvmField
-        val ISSUE_RECEIVER_EXPORTED_FLAG: Issue = Issue.create(
-                id = "UnspecifiedRegisterReceiverFlag",
-                briefDescription = "Missing `registerReceiver()` exported flag",
-                explanation = """
-                    Apps targeting Android T (SDK 33) and higher must specify either `RECEIVER_EXPORTED` \
-                    or `RECEIVER_NOT_EXPORTED` when registering a broadcast receiver, unless the \
-                    receiver is only registered for protected system broadcast actions.
-                    """,
-                category = Category.SECURITY,
-                priority = 5,
-                severity = Severity.WARNING,
-                implementation = Implementation(
-                        RegisterReceiverFlagDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
-        )
-
-        val PROTECTED_BROADCASTS = listOf(
-                "android.intent.action.SCREEN_OFF",
-                "android.intent.action.SCREEN_ON",
-                "android.intent.action.USER_PRESENT",
-                "android.intent.action.TIME_SET",
-                "android.intent.action.TIME_TICK",
-                "android.intent.action.TIMEZONE_CHANGED",
-                "android.intent.action.DATE_CHANGED",
-                "android.intent.action.PRE_BOOT_COMPLETED",
-                "android.intent.action.BOOT_COMPLETED",
-                "android.intent.action.PACKAGE_INSTALL",
-                "android.intent.action.PACKAGE_ADDED",
-                "android.intent.action.PACKAGE_REPLACED",
-                "android.intent.action.MY_PACKAGE_REPLACED",
-                "android.intent.action.PACKAGE_REMOVED",
-                "android.intent.action.PACKAGE_REMOVED_INTERNAL",
-                "android.intent.action.PACKAGE_FULLY_REMOVED",
-                "android.intent.action.PACKAGE_CHANGED",
-                "android.intent.action.PACKAGE_FULLY_LOADED",
-                "android.intent.action.PACKAGE_ENABLE_ROLLBACK",
-                "android.intent.action.CANCEL_ENABLE_ROLLBACK",
-                "android.intent.action.ROLLBACK_COMMITTED",
-                "android.intent.action.PACKAGE_RESTARTED",
-                "android.intent.action.PACKAGE_DATA_CLEARED",
-                "android.intent.action.PACKAGE_FIRST_LAUNCH",
-                "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION",
-                "android.intent.action.PACKAGE_NEEDS_VERIFICATION",
-                "android.intent.action.PACKAGE_VERIFIED",
-                "android.intent.action.PACKAGES_SUSPENDED",
-                "android.intent.action.PACKAGES_UNSUSPENDED",
-                "android.intent.action.PACKAGES_SUSPENSION_CHANGED",
-                "android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY",
-                "android.intent.action.DISTRACTING_PACKAGES_CHANGED",
-                "android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED",
-                "android.intent.action.UID_REMOVED",
-                "android.intent.action.QUERY_PACKAGE_RESTART",
-                "android.intent.action.CONFIGURATION_CHANGED",
-                "android.intent.action.SPLIT_CONFIGURATION_CHANGED",
-                "android.intent.action.LOCALE_CHANGED",
-                "android.intent.action.APPLICATION_LOCALE_CHANGED",
-                "android.intent.action.BATTERY_CHANGED",
-                "android.intent.action.BATTERY_LEVEL_CHANGED",
-                "android.intent.action.BATTERY_LOW",
-                "android.intent.action.BATTERY_OKAY",
-                "android.intent.action.ACTION_POWER_CONNECTED",
-                "android.intent.action.ACTION_POWER_DISCONNECTED",
-                "android.intent.action.ACTION_SHUTDOWN",
-                "android.intent.action.CHARGING",
-                "android.intent.action.DISCHARGING",
-                "android.intent.action.DEVICE_STORAGE_LOW",
-                "android.intent.action.DEVICE_STORAGE_OK",
-                "android.intent.action.DEVICE_STORAGE_FULL",
-                "android.intent.action.DEVICE_STORAGE_NOT_FULL",
-                "android.intent.action.NEW_OUTGOING_CALL",
-                "android.intent.action.REBOOT",
-                "android.intent.action.DOCK_EVENT",
-                "android.intent.action.THERMAL_EVENT",
-                "android.intent.action.MASTER_CLEAR_NOTIFICATION",
-                "android.intent.action.USER_ADDED",
-                "android.intent.action.USER_REMOVED",
-                "android.intent.action.USER_STARTING",
-                "android.intent.action.USER_STARTED",
-                "android.intent.action.USER_STOPPING",
-                "android.intent.action.USER_STOPPED",
-                "android.intent.action.USER_BACKGROUND",
-                "android.intent.action.USER_FOREGROUND",
-                "android.intent.action.USER_SWITCHED",
-                "android.intent.action.USER_INITIALIZE",
-                "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION",
-                "android.intent.action.DOMAINS_NEED_VERIFICATION",
-                "android.intent.action.OVERLAY_ADDED",
-                "android.intent.action.OVERLAY_CHANGED",
-                "android.intent.action.OVERLAY_REMOVED",
-                "android.intent.action.OVERLAY_PRIORITY_CHANGED",
-                "android.intent.action.MY_PACKAGE_SUSPENDED",
-                "android.intent.action.MY_PACKAGE_UNSUSPENDED",
-                "android.os.action.POWER_SAVE_MODE_CHANGED",
-                "android.os.action.DEVICE_IDLE_MODE_CHANGED",
-                "android.os.action.POWER_SAVE_WHITELIST_CHANGED",
-                "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED",
-                "android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL",
-                "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED",
-                "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED",
-                "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED",
-                "android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL",
-                "android.app.action.ENTER_CAR_MODE",
-                "android.app.action.EXIT_CAR_MODE",
-                "android.app.action.ENTER_CAR_MODE_PRIORITIZED",
-                "android.app.action.EXIT_CAR_MODE_PRIORITIZED",
-                "android.app.action.ENTER_DESK_MODE",
-                "android.app.action.EXIT_DESK_MODE",
-                "android.app.action.NEXT_ALARM_CLOCK_CHANGED",
-                "android.app.action.USER_ADDED",
-                "android.app.action.USER_REMOVED",
-                "android.app.action.USER_STARTED",
-                "android.app.action.USER_STOPPED",
-                "android.app.action.USER_SWITCHED",
-                "android.app.action.BUGREPORT_SHARING_DECLINED",
-                "android.app.action.BUGREPORT_FAILED",
-                "android.app.action.BUGREPORT_SHARE",
-                "android.app.action.SHOW_DEVICE_MONITORING_DIALOG",
-                "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED",
-                "android.intent.action.INCIDENT_REPORT_READY",
-                "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS",
-                "android.appwidget.action.APPWIDGET_DELETED",
-                "android.appwidget.action.APPWIDGET_DISABLED",
-                "android.appwidget.action.APPWIDGET_ENABLED",
-                "android.appwidget.action.APPWIDGET_HOST_RESTORED",
-                "android.appwidget.action.APPWIDGET_RESTORED",
-                "android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE",
-                "android.os.action.SETTING_RESTORED",
-                "android.app.backup.intent.CLEAR",
-                "android.app.backup.intent.INIT",
-                "android.bluetooth.intent.DISCOVERABLE_TIMEOUT",
-                "android.bluetooth.adapter.action.STATE_CHANGED",
-                "android.bluetooth.adapter.action.SCAN_MODE_CHANGED",
-                "android.bluetooth.adapter.action.DISCOVERY_STARTED",
-                "android.bluetooth.adapter.action.DISCOVERY_FINISHED",
-                "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED",
-                "android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED",
-                "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.device.action.UUID",
-                "android.bluetooth.device.action.MAS_INSTANCE",
-                "android.bluetooth.device.action.ALIAS_CHANGED",
-                "android.bluetooth.device.action.FOUND",
-                "android.bluetooth.device.action.CLASS_CHANGED",
-                "android.bluetooth.device.action.ACL_CONNECTED",
-                "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED",
-                "android.bluetooth.device.action.ACL_DISCONNECTED",
-                "android.bluetooth.device.action.NAME_CHANGED",
-                "android.bluetooth.device.action.BOND_STATE_CHANGED",
-                "android.bluetooth.device.action.NAME_FAILED",
-                "android.bluetooth.device.action.PAIRING_REQUEST",
-                "android.bluetooth.device.action.PAIRING_CANCEL",
-                "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY",
-                "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL",
-                "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST",
-                "android.bluetooth.device.action.SDP_RECORD",
-                "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED",
-                "android.bluetooth.devicepicker.action.LAUNCH",
-                "android.bluetooth.devicepicker.action.DEVICE_SELECTED",
-                "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED",
-                "android.bluetooth.action.CSIS_DEVICE_AVAILABLE",
-                "android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE",
-                "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_CONF_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED",
-                "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED",
-                "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION",
-                "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT",
-                "android.btopp.intent.action.LIST",
-                "android.btopp.intent.action.OPEN_OUTBOUND",
-                "android.btopp.intent.action.HIDE_COMPLETE",
-                "android.btopp.intent.action.CONFIRM",
-                "android.btopp.intent.action.HIDE",
-                "android.btopp.intent.action.RETRY",
-                "android.btopp.intent.action.OPEN",
-                "android.btopp.intent.action.OPEN_INBOUND",
-                "android.btopp.intent.action.TRANSFER_COMPLETE",
-                "android.btopp.intent.action.ACCEPT",
-                "android.btopp.intent.action.DECLINE",
-                "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN",
-                "com.android.bluetooth.pbap.authchall",
-                "com.android.bluetooth.pbap.userconfirmtimeout",
-                "com.android.bluetooth.pbap.authresponse",
-                "com.android.bluetooth.pbap.authcancelled",
-                "com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT",
-                "com.android.bluetooth.sap.action.DISCONNECT_ACTION",
-                "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED",
-                "android.hardware.usb.action.USB_STATE",
-                "android.hardware.usb.action.USB_PORT_CHANGED",
-                "android.hardware.usb.action.USB_ACCESSORY_ATTACHED",
-                "android.hardware.usb.action.USB_ACCESSORY_DETACHED",
-                "android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE",
-                "android.hardware.usb.action.USB_DEVICE_ATTACHED",
-                "android.hardware.usb.action.USB_DEVICE_DETACHED",
-                "android.intent.action.HEADSET_PLUG",
-                "android.media.action.HDMI_AUDIO_PLUG",
-                "android.media.action.MICROPHONE_MUTE_CHANGED",
-                "android.media.action.SPEAKERPHONE_STATE_CHANGED",
-                "android.media.AUDIO_BECOMING_NOISY",
-                "android.media.RINGER_MODE_CHANGED",
-                "android.media.VIBRATE_SETTING_CHANGED",
-                "android.media.VOLUME_CHANGED_ACTION",
-                "android.media.MASTER_VOLUME_CHANGED_ACTION",
-                "android.media.MASTER_MUTE_CHANGED_ACTION",
-                "android.media.MASTER_MONO_CHANGED_ACTION",
-                "android.media.MASTER_BALANCE_CHANGED_ACTION",
-                "android.media.SCO_AUDIO_STATE_CHANGED",
-                "android.media.ACTION_SCO_AUDIO_STATE_UPDATED",
-                "android.intent.action.MEDIA_REMOVED",
-                "android.intent.action.MEDIA_UNMOUNTED",
-                "android.intent.action.MEDIA_CHECKING",
-                "android.intent.action.MEDIA_NOFS",
-                "android.intent.action.MEDIA_MOUNTED",
-                "android.intent.action.MEDIA_SHARED",
-                "android.intent.action.MEDIA_UNSHARED",
-                "android.intent.action.MEDIA_BAD_REMOVAL",
-                "android.intent.action.MEDIA_UNMOUNTABLE",
-                "android.intent.action.MEDIA_EJECT",
-                "android.net.conn.CAPTIVE_PORTAL",
-                "android.net.conn.CONNECTIVITY_CHANGE",
-                "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE",
-                "android.net.conn.DATA_ACTIVITY_CHANGE",
-                "android.net.conn.RESTRICT_BACKGROUND_CHANGED",
-                "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED",
-                "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED",
-                "android.net.nsd.STATE_CHANGED",
-                "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED",
-                "android.nfc.action.ADAPTER_STATE_CHANGED",
-                "android.nfc.action.PREFERRED_PAYMENT_CHANGED",
-                "android.nfc.action.TRANSACTION_DETECTED",
-                "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC",
-                "com.android.nfc.action.LLCP_UP",
-                "com.android.nfc.action.LLCP_DOWN",
-                "com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG",
-                "com.android.nfc.handover.action.ALLOW_CONNECT",
-                "com.android.nfc.handover.action.DENY_CONNECT",
-                "com.android.nfc.handover.action.TIMEOUT_CONNECT",
-                "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED",
-                "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED",
-                "com.android.nfc_extras.action.AID_SELECTED",
-                "android.btopp.intent.action.WHITELIST_DEVICE",
-                "android.btopp.intent.action.STOP_HANDOVER_TRANSFER",
-                "android.nfc.handover.intent.action.HANDOVER_SEND",
-                "android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE",
-                "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER",
-                "android.net.action.CLEAR_DNS_CACHE",
-                "android.intent.action.PROXY_CHANGE",
-                "android.os.UpdateLock.UPDATE_LOCK_CHANGED",
-                "android.intent.action.DREAMING_STARTED",
-                "android.intent.action.DREAMING_STOPPED",
-                "android.intent.action.ANY_DATA_STATE",
-                "com.android.server.stats.action.TRIGGER_COLLECTION",
-                "com.android.server.WifiManager.action.START_SCAN",
-                "com.android.server.WifiManager.action.START_PNO",
-                "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP",
-                "com.android.server.WifiManager.action.DEVICE_IDLE",
-                "com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED",
-                "com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED",
-                "com.android.internal.action.EUICC_FACTORY_RESET",
-                "com.android.server.usb.ACTION_OPEN_IN_APPS",
-                "com.android.server.am.DELETE_DUMPHEAP",
-                "com.android.server.net.action.SNOOZE_WARNING",
-                "com.android.server.net.action.SNOOZE_RAPID",
-                "com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS",
-                "com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP",
-                "com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP",
-                "com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED",
-                "com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER",
-                "com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER",
-                "com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED",
-                "com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION",
-                "com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK",
-                "com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK",
-                "com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE",
-                "com.android.server.wifi.wakeup.DISMISS_NOTIFICATION",
-                "com.android.server.wifi.wakeup.OPEN_WIFI_PREFERENCES",
-                "com.android.server.wifi.wakeup.OPEN_WIFI_SETTINGS",
-                "com.android.server.wifi.wakeup.TURN_OFF_WIFI_WAKE",
-                "android.net.wifi.WIFI_STATE_CHANGED",
-                "android.net.wifi.WIFI_AP_STATE_CHANGED",
-                "android.net.wifi.WIFI_CREDENTIAL_CHANGED",
-                "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED",
-                "android.net.wifi.aware.action.WIFI_AWARE_RESOURCE_CHANGED",
-                "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED",
-                "android.net.wifi.SCAN_RESULTS",
-                "android.net.wifi.RSSI_CHANGED",
-                "android.net.wifi.STATE_CHANGE",
-                "android.net.wifi.LINK_CONFIGURATION_CHANGED",
-                "android.net.wifi.CONFIGURED_NETWORKS_CHANGE",
-                "android.net.wifi.action.NETWORK_SETTINGS_RESET",
-                "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT",
-                "android.net.wifi.action.PASSPOINT_ICON",
-                "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST",
-                "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION",
-                "android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW",
-                "android.net.wifi.action.REFRESH_USER_PROVISIONING",
-                "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION",
-                "android.net.wifi.action.WIFI_SCAN_AVAILABILITY_CHANGED",
-                "android.net.wifi.supplicant.CONNECTION_CHANGE",
-                "android.net.wifi.supplicant.STATE_CHANGE",
-                "android.net.wifi.p2p.STATE_CHANGED",
-                "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE",
-                "android.net.wifi.p2p.THIS_DEVICE_CHANGED",
-                "android.net.wifi.p2p.PEERS_CHANGED",
-                "android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
-                "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED",
-                "android.net.conn.TETHER_STATE_CHANGED",
-                "android.net.conn.INET_CONDITION_ACTION",
-                "android.net.conn.NETWORK_CONDITIONS_MEASURED",
-                "android.net.scoring.SCORE_NETWORKS",
-                "android.net.scoring.SCORER_CHANGED",
-                "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE",
-                "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE",
-                "android.intent.action.AIRPLANE_MODE",
-                "android.intent.action.ADVANCED_SETTINGS",
-                "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED",
-                "com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES",
-                "com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT",
-                "com.android.server.adb.WIRELESS_DEBUG_STATUS",
-                "android.intent.action.ACTION_IDLE_MAINTENANCE_START",
-                "android.intent.action.ACTION_IDLE_MAINTENANCE_END",
-                "com.android.server.ACTION_TRIGGER_IDLE",
-                "android.intent.action.HDMI_PLUGGED",
-                "android.intent.action.PHONE_STATE",
-                "android.intent.action.SUB_DEFAULT_CHANGED",
-                "android.location.PROVIDERS_CHANGED",
-                "android.location.MODE_CHANGED",
-                "android.location.action.GNSS_CAPABILITIES_CHANGED",
-                "android.net.proxy.PAC_REFRESH",
-                "android.telecom.action.DEFAULT_DIALER_CHANGED",
-                "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED",
-                "android.provider.action.SMS_MMS_DB_CREATED",
-                "android.provider.action.SMS_MMS_DB_LOST",
-                "android.intent.action.CONTENT_CHANGED",
-                "android.provider.Telephony.MMS_DOWNLOADED",
-                "android.content.action.PERMISSION_RESPONSE_RECEIVED",
-                "android.content.action.REQUEST_PERMISSION",
-                "android.nfc.handover.intent.action.HANDOVER_STARTED",
-                "android.nfc.handover.intent.action.TRANSFER_DONE",
-                "android.nfc.handover.intent.action.TRANSFER_PROGRESS",
-                "android.nfc.handover.intent.action.TRANSFER_DONE",
-                "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED",
-                "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED",
-                "android.intent.action.ACTION_SET_RADIO_CAPABILITY_DONE",
-                "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED",
-                "android.internal.policy.action.BURN_IN_PROTECTION",
-                "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED",
-                "android.app.action.RESET_PROTECTION_POLICY_CHANGED",
-                "android.app.action.DEVICE_OWNER_CHANGED",
-                "android.app.action.MANAGED_USER_CREATED",
-                "android.intent.action.ANR",
-                "android.intent.action.CALL",
-                "android.intent.action.CALL_PRIVILEGED",
-                "android.intent.action.DROPBOX_ENTRY_ADDED",
-                "android.intent.action.INPUT_METHOD_CHANGED",
-                "android.intent.action.internal_sim_state_changed",
-                "android.intent.action.LOCKED_BOOT_COMPLETED",
-                "android.intent.action.PRECISE_CALL_STATE",
-                "android.intent.action.SUBSCRIPTION_PHONE_STATE",
-                "android.intent.action.USER_INFO_CHANGED",
-                "android.intent.action.USER_UNLOCKED",
-                "android.intent.action.WALLPAPER_CHANGED",
-                "android.app.action.DEVICE_POLICY_MANAGER_STATE_CHANGED",
-                "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS",
-                "android.app.action.DEVICE_ADMIN_DISABLED",
-                "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED",
-                "android.app.action.DEVICE_ADMIN_ENABLED",
-                "android.app.action.LOCK_TASK_ENTERING",
-                "android.app.action.LOCK_TASK_EXITING",
-                "android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE",
-                "android.app.action.ACTION_PASSWORD_CHANGED",
-                "android.app.action.ACTION_PASSWORD_EXPIRING",
-                "android.app.action.ACTION_PASSWORD_FAILED",
-                "android.app.action.ACTION_PASSWORD_SUCCEEDED",
-                "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION",
-                "com.android.server.ACTION_PROFILE_OFF_DEADLINE",
-                "com.android.server.ACTION_TURN_PROFILE_ON_NOTIFICATION",
-                "android.intent.action.MANAGED_PROFILE_ADDED",
-                "android.intent.action.MANAGED_PROFILE_UNLOCKED",
-                "android.intent.action.MANAGED_PROFILE_REMOVED",
-                "android.app.action.MANAGED_PROFILE_PROVISIONED",
-                "android.bluetooth.adapter.action.BLE_STATE_CHANGED",
-                "com.android.bluetooth.map.USER_CONFIRM_TIMEOUT",
-                "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT",
-                "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY",
-                "android.content.jobscheduler.JOB_DELAY_EXPIRED",
-                "android.content.syncmanager.SYNC_ALARM",
-                "android.media.INTERNAL_RINGER_MODE_CHANGED_ACTION",
-                "android.media.STREAM_DEVICES_CHANGED_ACTION",
-                "android.media.STREAM_MUTE_CHANGED_ACTION",
-                "android.net.sip.SIP_SERVICE_UP",
-                "android.nfc.action.ADAPTER_STATE_CHANGED",
-                "android.os.action.CHARGING",
-                "android.os.action.DISCHARGING",
-                "android.search.action.SEARCHABLES_CHANGED",
-                "android.security.STORAGE_CHANGED",
-                "android.security.action.TRUST_STORE_CHANGED",
-                "android.security.action.KEYCHAIN_CHANGED",
-                "android.security.action.KEY_ACCESS_CHANGED",
-                "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED",
-                "android.telecom.action.PHONE_ACCOUNT_REGISTERED",
-                "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED",
-                "android.telecom.action.POST_CALL",
-                "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION",
-                "android.telephony.action.CARRIER_CONFIG_CHANGED",
-                "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED",
-                "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED",
-                "android.telephony.action.SECRET_CODE",
-                "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION",
-                "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED",
-                "com.android.bluetooth.btservice.action.ALARM_WAKEUP",
-                "com.android.server.action.NETWORK_STATS_POLL",
-                "com.android.server.action.NETWORK_STATS_UPDATED",
-                "com.android.server.timedetector.NetworkTimeUpdateService.action.POLL",
-                "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY",
-                "com.android.settings.location.MODE_CHANGING",
-                "com.android.settings.bluetooth.ACTION_DISMISS_PAIRING",
-                "com.android.settings.network.DELETE_SUBSCRIPTION",
-                "com.android.settings.network.SWITCH_TO_SUBSCRIPTION",
-                "com.android.settings.wifi.action.NETWORK_REQUEST",
-                "NotificationManagerService.TIMEOUT",
-                "NotificationHistoryDatabase.CLEANUP",
-                "ScheduleConditionProvider.EVALUATE",
-                "EventConditionProvider.EVALUATE",
-                "SnoozeHelper.EVALUATE",
-                "wifi_scan_available",
-                "action.cne.started",
-                "android.content.jobscheduler.JOB_DEADLINE_EXPIRED",
-                "android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW",
-                "android.net.conn.CONNECTIVITY_CHANGE_SUPL",
-                "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED",
-                "android.os.storage.action.VOLUME_STATE_CHANGED",
-                "android.os.storage.action.DISK_SCANNED",
-                "com.android.server.action.UPDATE_TWILIGHT_STATE",
-                "com.android.server.action.RESET_TWILIGHT_AUTO",
-                "com.android.server.device_idle.STEP_IDLE_STATE",
-                "com.android.server.device_idle.STEP_LIGHT_IDLE_STATE",
-                "com.android.server.Wifi.action.TOGGLE_PNO",
-                "intent.action.ACTION_RF_BAND_INFO",
-                "android.intent.action.MEDIA_RESOURCE_GRANTED",
-                "android.app.action.NETWORK_LOGS_AVAILABLE",
-                "android.app.action.SECURITY_LOGS_AVAILABLE",
-                "android.app.action.COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED",
-                "android.app.action.INTERRUPTION_FILTER_CHANGED",
-                "android.app.action.INTERRUPTION_FILTER_CHANGED_INTERNAL",
-                "android.app.action.NOTIFICATION_POLICY_CHANGED",
-                "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED",
-                "android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED",
-                "android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED",
-                "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED",
-                "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED",
-                "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED",
-                "android.app.action.APP_BLOCK_STATE_CHANGED",
-                "android.permission.GET_APP_GRANTED_URI_PERMISSIONS",
-                "android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS",
-                "android.intent.action.DYNAMIC_SENSOR_CHANGED",
-                "android.accounts.LOGIN_ACCOUNTS_CHANGED",
-                "android.accounts.action.ACCOUNT_REMOVED",
-                "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED",
-                "com.android.sync.SYNC_CONN_STATUS_CHANGED",
-                "android.net.sip.action.SIP_INCOMING_CALL",
-                "com.android.phone.SIP_ADD_PHONE",
-                "android.net.sip.action.SIP_REMOVE_PROFILE",
-                "android.net.sip.action.SIP_SERVICE_UP",
-                "android.net.sip.action.SIP_CALL_OPTION_CHANGED",
-                "android.net.sip.action.START_SIP",
-                "android.bluetooth.adapter.action.BLE_ACL_CONNECTED",
-                "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED",
-                "android.bluetooth.input.profile.action.HANDSHAKE",
-                "android.bluetooth.input.profile.action.REPORT",
-                "android.intent.action.TWILIGHT_CHANGED",
-                "com.android.server.fingerprint.ACTION_LOCKOUT_RESET",
-                "android.net.wifi.PASSPOINT_ICON_RECEIVED",
-                "com.android.server.notification.CountdownConditionProvider",
-                "android.server.notification.action.ENABLE_NAS",
-                "android.server.notification.action.DISABLE_NAS",
-                "android.server.notification.action.LEARNMORE_NAS",
-                "com.android.internal.location.ALARM_WAKEUP",
-                "com.android.internal.location.ALARM_TIMEOUT",
-                "android.intent.action.GLOBAL_BUTTON",
-                "android.intent.action.MANAGED_PROFILE_AVAILABLE",
-                "android.intent.action.MANAGED_PROFILE_UNAVAILABLE",
-                "com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK",
-                "android.intent.action.PROFILE_ACCESSIBLE",
-                "android.intent.action.PROFILE_INACCESSIBLE",
-                "com.android.server.retaildemo.ACTION_RESET_DEMO",
-                "android.intent.action.DEVICE_LOCKED_CHANGED",
-                "com.android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED",
-                "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED",
-                "com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION",
-                "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED",
-                "android.content.pm.action.SESSION_COMMITTED",
-                "android.os.action.USER_RESTRICTIONS_CHANGED",
-                "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT",
-                "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED",
-                "android.media.tv.action.WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED",
-                "android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED",
-                "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER",
-                "com.android.intent.action.timezone.RULES_UPDATE_OPERATION",
-                "com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK",
-                "android.intent.action.GET_RESTRICTION_ENTRIES",
-                "android.telephony.euicc.action.OTA_STATUS_CHANGED",
-                "android.app.action.PROFILE_OWNER_CHANGED",
-                "android.app.action.TRANSFER_OWNERSHIP_COMPLETE",
-                "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE",
-                "android.app.action.STATSD_STARTED",
-                "com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET",
-                "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET",
-                "android.intent.action.DOCK_IDLE",
-                "android.intent.action.DOCK_ACTIVE",
-                "android.content.pm.action.SESSION_UPDATED",
-                "android.settings.action.GRAYSCALE_CHANGED",
-                "com.android.server.jobscheduler.GARAGE_MODE_ON",
-                "com.android.server.jobscheduler.GARAGE_MODE_OFF",
-                "com.android.server.jobscheduler.FORCE_IDLE",
-                "com.android.server.jobscheduler.UNFORCE_IDLE",
-                "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL",
-                "android.intent.action.DEVICE_CUSTOMIZATION_READY",
-                "android.app.action.RESET_PROTECTION_POLICY_CHANGED",
-                "com.android.internal.intent.action.BUGREPORT_REQUESTED",
-                "android.scheduling.action.REBOOT_READY",
-                "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED",
-                "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED",
-                "android.app.action.SHOW_NEW_USER_DISCLAIMER",
-                "android.telecom.action.CURRENT_TTY_MODE_CHANGED",
-                "android.intent.action.SERVICE_STATE",
-                "android.intent.action.RADIO_TECHNOLOGY",
-                "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED",
-                "android.intent.action.EMERGENCY_CALL_STATE_CHANGED",
-                "android.intent.action.SIG_STR",
-                "android.intent.action.ANY_DATA_STATE",
-                "android.intent.action.DATA_STALL_DETECTED",
-                "android.intent.action.SIM_STATE_CHANGED",
-                "android.intent.action.USER_ACTIVITY_NOTIFICATION",
-                "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS",
-                "android.intent.action.ACTION_MDN_STATE_CHANGED",
-                "android.telephony.action.SERVICE_PROVIDERS_UPDATED",
-                "android.provider.Telephony.SIM_FULL",
-                "com.android.internal.telephony.carrier_key_download_alarm",
-                "com.android.internal.telephony.data-restart-trysetup",
-                "com.android.internal.telephony.data-stall",
-                "com.android.internal.telephony.provisioning_apn_alarm",
-                "android.intent.action.DATA_SMS_RECEIVED",
-                "android.provider.Telephony.SMS_RECEIVED",
-                "android.provider.Telephony.SMS_DELIVER",
-                "android.provider.Telephony.SMS_REJECTED",
-                "android.provider.Telephony.WAP_PUSH_DELIVER",
-                "android.provider.Telephony.WAP_PUSH_RECEIVED",
-                "android.provider.Telephony.SMS_CB_RECEIVED",
-                "android.provider.action.SMS_EMERGENCY_CB_RECEIVED",
-                "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED",
-                "android.provider.Telephony.SECRET_CODE",
-                "com.android.internal.stk.command",
-                "com.android.internal.stk.session_end",
-                "com.android.internal.stk.icc_status_change",
-                "com.android.internal.stk.alpha_notify",
-                "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED",
-                "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED",
-                "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE",
-                "com.android.internal.telephony.CARRIER_SIGNAL_RESET",
-                "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE",
-                "com.android.internal.telephony.PROVISION",
-                "com.android.internal.telephony.ACTION_LINE1_NUMBER_ERROR_DETECTED",
-                "com.android.internal.provider.action.VOICEMAIL_SMS_RECEIVED",
-                "com.android.intent.isim_refresh",
-                "com.android.ims.ACTION_RCS_SERVICE_AVAILABLE",
-                "com.android.ims.ACTION_RCS_SERVICE_UNAVAILABLE",
-                "com.android.ims.ACTION_RCS_SERVICE_DIED",
-                "com.android.ims.ACTION_PRESENCE_CHANGED",
-                "com.android.ims.ACTION_PUBLISH_STATUS_CHANGED",
-                "com.android.ims.IMS_SERVICE_UP",
-                "com.android.ims.IMS_SERVICE_DOWN",
-                "com.android.ims.IMS_INCOMING_CALL",
-                "com.android.ims.internal.uce.UCE_SERVICE_UP",
-                "com.android.ims.internal.uce.UCE_SERVICE_DOWN",
-                "com.android.imsconnection.DISCONNECTED",
-                "com.android.intent.action.IMS_FEATURE_CHANGED",
-                "com.android.intent.action.IMS_CONFIG_CHANGED",
-                "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR",
-                "com.android.phone.vvm.omtp.sms.REQUEST_SENT",
-                "com.android.phone.vvm.ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT",
-                "com.android.internal.telephony.CARRIER_VVM_PACKAGE_INSTALLED",
-                "com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO",
-                "com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD",
-                "com.android.internal.telephony.action.COUNTRY_OVERRIDE",
-                "com.android.internal.telephony.OPEN_DEFAULT_SMS_APP",
-                "com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID",
-                "android.telephony.action.SIM_CARD_STATE_CHANGED",
-                "android.telephony.action.SIM_APPLICATION_STATE_CHANGED",
-                "android.telephony.action.SIM_SLOT_STATUS_CHANGED",
-                "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED",
-                "android.telephony.action.SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED",
-                "android.telephony.action.TOGGLE_PROVISION",
-                "android.telephony.action.NETWORK_COUNTRY_CHANGED",
-                "android.telephony.action.PRIMARY_SUBSCRIPTION_LIST_CHANGED",
-                "android.telephony.action.MULTI_SIM_CONFIG_CHANGED",
-                "android.telephony.action.CARRIER_SIGNAL_RESET",
-                "android.telephony.action.CARRIER_SIGNAL_PCO_VALUE",
-                "android.telephony.action.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE",
-                "android.telephony.action.CARRIER_SIGNAL_REDIRECTED",
-                "android.telephony.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED",
-                "com.android.phone.settings.CARRIER_PROVISIONING",
-                "com.android.phone.settings.TRIGGER_CARRIER_PROVISIONING",
-                "com.android.internal.telephony.ACTION_VOWIFI_ENABLED",
-                "android.telephony.action.ANOMALY_REPORTED",
-                "android.intent.action.SUBSCRIPTION_INFO_RECORD_ADDED",
-                "android.intent.action.ACTION_MANAGED_ROAMING_IND",
-                "android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE",
-                "android.safetycenter.action.REFRESH_SAFETY_SOURCES",
-                "android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED",
-                "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED",
-                "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER",
-                "android.service.autofill.action.DELAYED_FILL",
-                "android.app.action.PROVISIONING_COMPLETED",
-                "android.app.action.LOST_MODE_LOCATION_UPDATE",
-                "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED",
-                "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT",
-                "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED",
-                "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED",
-                "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED",
-                "android.bluetooth.headsetclient.profile.action.AG_EVENT",
-                "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED",
-                "android.bluetooth.headsetclient.profile.action.RESULT",
-                "android.bluetooth.headsetclient.profile.action.LAST_VTAG",
-                "android.bluetooth.headsetclient.profile.action.NETWORK_SERVICE_STATE_CHANGED",
-                "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED",
-                "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED",
-                "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED",
-                "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED",
-                "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED",
-                "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED",
-                "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED",
-                "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED",
-                "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST",
-                "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT",
-                "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED",
-                "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED",
-                "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS",
-                "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED",
-                "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT",
-                "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY",
-                "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.action.TETHERING_STATE_CHANGED",
-                "com.android.internal.action.EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS",
-                "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED",
-                "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION",
-                "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM"
-        )
-    }
-}
diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
deleted file mode 100644
index 7c0ebca..0000000
--- a/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
+++ /dev/null
@@ -1,569 +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.google.android.lint
-
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
-import com.android.tools.lint.checks.infrastructure.TestLintTask
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Issue
-
-@Suppress("UnstableApiUsage")
-class RegisterReceiverFlagDetectorTest : LintDetectorTest() {
-
-    override fun getDetector(): Detector = RegisterReceiverFlagDetector()
-
-    override fun getIssues(): List<Issue> = listOf(
-            RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG
-    )
-
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
-
-    fun testProtectedBroadcast() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-                            context.registerReceiver(receiver, filter);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    fun testProtectedBroadcastCreate() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter =
-                                    IntentFilter.create(Intent.ACTION_BATTERY_CHANGED, "foo/bar");
-                            context.registerReceiver(receiver, filter);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    fun testMultipleProtectedBroadcasts() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-                            filter.addAction(Intent.ACTION_BATTERY_LOW);
-                            filter.addAction(Intent.ACTION_BATTERY_OKAY);
-                            context.registerReceiver(receiver, filter);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testSubsequentFilterModification() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver) {
-    //                         IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-    //                         filter.addAction(Intent.ACTION_BATTERY_LOW);
-    //                         filter.addAction(Intent.ACTION_BATTERY_OKAY);
-    //                         context.registerReceiver(receiver, filter);
-    //                         filter.addAction("querty");
-    //                         context.registerReceiver(receiver, filter);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:13: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter);
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    fun testNullReceiver() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context) {
-                            IntentFilter filter = new IntentFilter("qwerty");
-                            context.registerReceiver(null, filter);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    fun testExportedFlagPresent() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter("qwerty");
-                            context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    fun testNotExportedFlagPresent() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter("qwerty");
-                            context.registerReceiver(receiver, filter,
-                                    Context.RECEIVER_NOT_EXPORTED);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFlagArgumentAbsent() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver) {
-    //                         IntentFilter filter = new IntentFilter("qwerty");
-    //                         context.registerReceiver(receiver, filter);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter);
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testExportedFlagsAbsent() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver) {
-    //                         IntentFilter filter = new IntentFilter("qwerty");
-    //                         context.registerReceiver(receiver, filter, 0);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter, 0);
-    //                                                                ~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    fun testExportedFlagVariable() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter("qwerty");
-                            var flags = Context.RECEIVER_EXPORTED;
-                            context.registerReceiver(receiver, filter, flags);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testUnknownFilter() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver,
-    //                             IntentFilter filter) {
-    //                         context.registerReceiver(receiver, filter);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter);
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFilterEscapes() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver) {
-    //                         IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-    //                         updateFilter(filter);
-    //                         context.registerReceiver(receiver, filter);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:10: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter);
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    fun testInlineFilter() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            context.registerReceiver(receiver,
-                                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testInlineFilterApply() {
-    //     lint().files(
-    //             kotlin(
-    //                     """
-    //                 package test.pkg
-    //                 import android.content.BroadcastReceiver
-    //                 import android.content.Context
-    //                 import android.content.Intent
-    //                 import android.content.IntentFilter
-    //                 class TestClass1 {
-    //                     fun test(context: Context, receiver: BroadcastReceiver) {
-    //                         context.registerReceiver(receiver,
-    //                                 IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
-    //                                     addAction("qwerty")
-    //                                 })
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.kt:8: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver,
-    //                     ^
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFilterVariableApply() {
-    //     lint().files(
-    //             kotlin(
-    //                     """
-    //                 package test.pkg
-    //                 import android.content.BroadcastReceiver
-    //                 import android.content.Context
-    //                 import android.content.Intent
-    //                 import android.content.IntentFilter
-    //                 class TestClass1 {
-    //                     fun test(context: Context, receiver: BroadcastReceiver) {
-    //                         val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
-    //                             addAction("qwerty")
-    //                         }
-    //                         context.registerReceiver(receiver, filter)
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.kt:11: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter)
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFilterVariableApply2() {
-    //     lint().files(
-    //             kotlin(
-    //                     """
-    //                 package test.pkg
-    //                 import android.content.BroadcastReceiver
-    //                 import android.content.Context
-    //                 import android.content.Intent
-    //                 import android.content.IntentFilter
-    //                 class TestClass1 {
-    //                     fun test(context: Context, receiver: BroadcastReceiver) {
-    //                         val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
-    //                             addAction(Intent.ACTION_BATTERY_OKAY)
-    //                         }
-    //                         context.registerReceiver(receiver, filter.apply {
-    //                             addAction("qwerty")
-    //                         })
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.kt:11: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter.apply {
-    //                     ^
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFilterComplexChain() {
-    //     lint().files(
-    //             kotlin(
-    //                     """
-    //                 package test.pkg
-    //                 import android.content.BroadcastReceiver
-    //                 import android.content.Context
-    //                 import android.content.Intent
-    //                 import android.content.IntentFilter
-    //                 class TestClass1 {
-    //                     fun test(context: Context, receiver: BroadcastReceiver) {
-    //                         val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
-    //                             addAction(Intent.ACTION_BATTERY_OKAY)
-    //                         }
-    //                         val filter2 = filter
-    //                         val filter3 = filter2.apply {
-    //                             addAction(Intent.ACTION_BATTERY_LOW)
-    //                         }
-    //                         context.registerReceiver(receiver, filter3)
-    //                         val filter4 = filter3.apply {
-    //                             addAction("qwerty")
-    //                         }
-    //                         context.registerReceiver(receiver, filter4)
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.kt:19: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter4)
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    private val broadcastReceiverStub: TestFile = java(
-            """
-            package android.content;
-            public class BroadcastReceiver {
-                // Stub
-            }
-            """
-    ).indented()
-
-    private val contextStub: TestFile = java(
-            """
-            package android.content;
-            public class Context {
-                public static final int RECEIVER_EXPORTED = 0x2;
-                public static final int RECEIVER_NOT_EXPORTED = 0x4;
-                @Nullable
-                public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
-                                                        IntentFilter filter,
-                                                        @RegisterReceiverFlags int flags);
-            }
-            """
-    ).indented()
-
-    private val intentStub: TestFile = java(
-            """
-            package android.content;
-            public class Intent {
-                public static final String ACTION_BATTERY_CHANGED =
-                        "android.intent.action.BATTERY_CHANGED";
-                public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
-                public static final String ACTION_BATTERY_OKAY =
-                        "android.intent.action.BATTERY_OKAY";
-            }
-            """
-    ).indented()
-
-    private val intentFilterStub: TestFile = java(
-            """
-            package android.content;
-            public class IntentFilter {
-                public IntentFilter() {
-                    // Stub
-                }
-                public IntentFilter(String action) {
-                    // Stub
-                }
-                public IntentFilter(String action, String dataType) {
-                    // Stub
-                }
-                public static IntentFilter create(String action, String dataType) {
-                    return null;
-                }
-                public final void addAction(String action) {
-                    // Stub
-                }
-            }
-            """
-    ).indented()
-
-    private val stubs = arrayOf(broadcastReceiverStub, contextStub, intentStub, intentFilterStub)
-}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java
index 9bfeb63..fe397d9 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
@@ -86,6 +87,7 @@
     @Nullable
     @SecurityType
     private final ArraySet<Integer> mHotspotSecurityTypes;
+    private final Bundle mExtras;
 
     /**
      * Builder class for {@link HotspotNetwork}.
@@ -102,8 +104,8 @@
         private String mHotspotBssid;
         @Nullable
         @SecurityType
-        private final ArraySet<Integer> mHotspotSecurityTypes =
-                new ArraySet<>();
+        private final ArraySet<Integer> mHotspotSecurityTypes = new ArraySet<>();
+        private Bundle mExtras = Bundle.EMPTY;
 
         /**
          * Set the remote device ID.
@@ -190,6 +192,17 @@
         }
 
         /**
+         * Sets the extras bundle
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds the {@link HotspotNetwork} object.
          *
          * @return Returns the built {@link HotspotNetwork} object.
@@ -203,7 +216,8 @@
                     mNetworkName,
                     mHotspotSsid,
                     mHotspotBssid,
-                    mHotspotSecurityTypes);
+                    mHotspotSecurityTypes,
+                    mExtras);
         }
     }
 
@@ -231,7 +245,8 @@
             @NonNull String networkName,
             @Nullable String hotspotSsid,
             @Nullable String hotspotBssid,
-            @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes) {
+            @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes,
+            @NonNull Bundle extras) {
         validate(deviceId,
                 networkType,
                 networkName,
@@ -243,6 +258,7 @@
         mHotspotSsid = hotspotSsid;
         mHotspotBssid = hotspotBssid;
         mHotspotSecurityTypes = new ArraySet<>(hotspotSecurityTypes);
+        mExtras = extras;
     }
 
     /**
@@ -315,6 +331,16 @@
         return mHotspotSecurityTypes;
     }
 
+    /**
+     * Gets the extras Bundle.
+     *
+     * @return Returns a Bundle object.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof HotspotNetwork)) return false;
@@ -348,6 +374,7 @@
         dest.writeString(mHotspotSsid);
         dest.writeString(mHotspotBssid);
         dest.writeArraySet(mHotspotSecurityTypes);
+        dest.writeBundle(mExtras);
     }
 
     /**
@@ -359,7 +386,7 @@
     public static HotspotNetwork readFromParcel(@NonNull Parcel in) {
         return new HotspotNetwork(in.readLong(), NetworkProviderInfo.readFromParcel(in),
                 in.readInt(), in.readString(), in.readString(), in.readString(),
-                (ArraySet<Integer>) in.readArraySet(null));
+                (ArraySet<Integer>) in.readArraySet(null), in.readBundle());
     }
 
     @NonNull
@@ -385,6 +412,7 @@
                 .append(", hotspotSsid=").append(mHotspotSsid)
                 .append(", hotspotBssid=").append(mHotspotBssid)
                 .append(", hotspotSecurityTypes=").append(mHotspotSecurityTypes.toString())
+                .append(", extras=").append(mExtras.toString())
                 .append("]").toString();
     }
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java
index 69767f3..72acf2c 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java
@@ -117,7 +117,7 @@
         @ConnectionStatus
         private int mStatus;
         private HotspotNetwork mHotspotNetwork;
-        private Bundle mExtras;
+        private Bundle mExtras = Bundle.EMPTY;
 
         /**
          * Sets the status of the connection
@@ -179,7 +179,7 @@
     }
 
     private HotspotNetworkConnectionStatus(@ConnectionStatus int status,
-            HotspotNetwork hotspotNetwork, Bundle extras) {
+            HotspotNetwork hotspotNetwork, @NonNull Bundle extras) {
         validate(status);
         mStatus = status;
         mHotspotNetwork = hotspotNetwork;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
index 64412bc..c390e42 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -73,6 +74,7 @@
     @SecurityType
     private final ArraySet<Integer> mSecurityTypes;
     private final NetworkProviderInfo mNetworkProviderInfo;
+    private final Bundle mExtras;
 
     /**
      * Builder class for {@link KnownNetwork}.
@@ -84,6 +86,7 @@
         @SecurityType
         private final ArraySet<Integer> mSecurityTypes = new ArraySet<>();
         private NetworkProviderInfo mNetworkProviderInfo;
+        private Bundle mExtras = Bundle.EMPTY;
 
         /**
          * Sets the indicated source of the known network.
@@ -135,6 +138,17 @@
         }
 
         /**
+         * Sets the extras bundle
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds the {@link KnownNetwork} object.
          *
          * @return Returns the built {@link KnownNetwork} object.
@@ -145,7 +159,8 @@
                     mNetworkSource,
                     mSsid,
                     mSecurityTypes,
-                    mNetworkProviderInfo);
+                    mNetworkProviderInfo,
+                    mExtras);
         }
     }
 
@@ -173,12 +188,14 @@
             @NetworkSource int networkSource,
             @NonNull String ssid,
             @NonNull @SecurityType ArraySet<Integer> securityTypes,
-            @Nullable NetworkProviderInfo networkProviderInfo) {
+            @Nullable NetworkProviderInfo networkProviderInfo,
+            @NonNull Bundle extras) {
         validate(networkSource, ssid, securityTypes, networkProviderInfo);
         mNetworkSource = networkSource;
         mSsid = ssid;
         mSecurityTypes = new ArraySet<>(securityTypes);
         mNetworkProviderInfo = networkProviderInfo;
+        mExtras = extras;
     }
 
     /**
@@ -223,6 +240,16 @@
         return mNetworkProviderInfo;
     }
 
+    /**
+     * Gets the extras Bundle.
+     *
+     * @return Returns a Bundle object.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof KnownNetwork)) return false;
@@ -249,6 +276,7 @@
         dest.writeString(mSsid);
         dest.writeArraySet(mSecurityTypes);
         mNetworkProviderInfo.writeToParcel(dest, flags);
+        dest.writeBundle(mExtras);
     }
 
     /**
@@ -260,7 +288,7 @@
     public static KnownNetwork readFromParcel(@NonNull Parcel in) {
         return new KnownNetwork(in.readInt(), in.readString(),
                 (ArraySet<Integer>) in.readArraySet(null),
-                NetworkProviderInfo.readFromParcel(in));
+                NetworkProviderInfo.readFromParcel(in), in.readBundle());
     }
 
     @NonNull
@@ -283,6 +311,7 @@
                 .append(", ssid=").append(mSsid)
                 .append(", securityTypes=").append(mSecurityTypes.toString())
                 .append(", networkProviderInfo=").append(mNetworkProviderInfo.toString())
+                .append(", extras=").append(mExtras.toString())
                 .append("]").toString();
     }
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
index 6bd0a5e..b30dc3f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
@@ -72,7 +72,7 @@
     public static final class Builder {
         @ConnectionStatus private int mStatus;
         private KnownNetwork mKnownNetwork;
-        private Bundle mExtras;
+        private Bundle mExtras = Bundle.EMPTY;
 
         public Builder() {}
 
@@ -128,7 +128,7 @@
     }
 
     private KnownNetworkConnectionStatus(@ConnectionStatus int status, KnownNetwork knownNetwork,
-            Bundle extras) {
+            @NonNull Bundle extras) {
         validate(status);
         mStatus = status;
         mKnownNetwork = knownNetwork;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index ed4d699..25fbabc 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -89,6 +90,7 @@
     private final String mModelName;
     private final int mBatteryPercentage;
     private final int mConnectionStrength;
+    private final Bundle mExtras;
 
     /**
      * Builder class for {@link NetworkProviderInfo}.
@@ -99,6 +101,7 @@
         private String mModelName;
         private int mBatteryPercentage;
         private int mConnectionStrength;
+        private Bundle mExtras = Bundle.EMPTY;
 
         public Builder(@NonNull String deviceName, @NonNull String modelName) {
             Objects.requireNonNull(deviceName);
@@ -170,6 +173,17 @@
         }
 
         /**
+         * Sets the extras bundle
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds the {@link NetworkProviderInfo} object.
          *
          * @return Returns the built {@link NetworkProviderInfo} object.
@@ -177,7 +191,7 @@
         @NonNull
         public NetworkProviderInfo build() {
             return new NetworkProviderInfo(mDeviceType, mDeviceName, mModelName, mBatteryPercentage,
-                    mConnectionStrength);
+                    mConnectionStrength, mExtras);
         }
     }
 
@@ -197,13 +211,15 @@
     }
 
     private NetworkProviderInfo(@DeviceType int deviceType, @NonNull String deviceName,
-            @NonNull String modelName, int batteryPercentage, int connectionStrength) {
+            @NonNull String modelName, int batteryPercentage, int connectionStrength,
+            @NonNull Bundle extras) {
         validate(deviceType, deviceName, modelName, batteryPercentage, connectionStrength);
         mDeviceType = deviceType;
         mDeviceName = deviceName;
         mModelName = modelName;
         mBatteryPercentage = batteryPercentage;
         mConnectionStrength = connectionStrength;
+        mExtras = extras;
     }
 
     /**
@@ -256,6 +272,16 @@
         return mConnectionStrength;
     }
 
+    /**
+     * Gets the extras Bundle.
+     *
+     * @return Returns a Bundle object.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof NetworkProviderInfo)) return false;
@@ -280,6 +306,7 @@
         dest.writeString(mModelName);
         dest.writeInt(mBatteryPercentage);
         dest.writeInt(mConnectionStrength);
+        dest.writeBundle(mExtras);
     }
 
     @Override
@@ -295,7 +322,7 @@
     @NonNull
     public static NetworkProviderInfo readFromParcel(@NonNull Parcel in) {
         return new NetworkProviderInfo(in.readInt(), in.readString(), in.readString(), in.readInt(),
-                in.readInt());
+                in.readInt(), in.readBundle());
     }
 
     @NonNull
@@ -319,6 +346,7 @@
                 .append(", modelName=").append(mModelName)
                 .append(", batteryPercentage=").append(mBatteryPercentage)
                 .append(", connectionStrength=").append(mConnectionStrength)
+                .append(", extras=").append(mExtras.toString())
                 .append("]").toString();
     }
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index 4809bef..30bb989 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -17,7 +17,11 @@
 package android.net.wifi.sharedconnectivity.app;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -37,6 +41,7 @@
 public final class SharedConnectivitySettingsState implements Parcelable {
 
     private final boolean mInstantTetherEnabled;
+    private final PendingIntent mInstantTetherSettingsPendingIntent;
     private final Bundle mExtras;
 
     /**
@@ -44,9 +49,13 @@
      */
     public static final class Builder {
         private boolean mInstantTetherEnabled;
-        private Bundle mExtras;
+        private Intent mInstantTetherSettingsIntent;
+        private final Context mContext;
+        private Bundle mExtras = Bundle.EMPTY;
 
-        public Builder() {}
+        public Builder(@NonNull Context context) {
+            mContext = context;
+        }
 
         /**
          * Sets the state of Instant Tether in settings
@@ -60,6 +69,20 @@
         }
 
         /**
+         * Sets the intent that will open the Instant Tether settings page.
+         * The intent will be stored as a {@link PendingIntent} in the settings object. The pending
+         * intent will be set as {@link PendingIntent#FLAG_IMMUTABLE} and
+         * {@link PendingIntent#FLAG_ONE_SHOT}.
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setInstantTetherSettingsPendingIntent(@NonNull Intent intent) {
+            mInstantTetherSettingsIntent = intent;
+            return this;
+        }
+
+        /**
          * Sets the extras bundle
          *
          * @return Returns the Builder object.
@@ -77,12 +100,21 @@
          */
         @NonNull
         public SharedConnectivitySettingsState build() {
-            return new SharedConnectivitySettingsState(mInstantTetherEnabled, mExtras);
+            if (mInstantTetherSettingsIntent != null) {
+                PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
+                        mInstantTetherSettingsIntent,
+                        PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
+                return new SharedConnectivitySettingsState(mInstantTetherEnabled,
+                        pendingIntent, mExtras);
+            }
+            return new SharedConnectivitySettingsState(mInstantTetherEnabled, null, mExtras);
         }
     }
 
-    private SharedConnectivitySettingsState(boolean instantTetherEnabled, Bundle extras) {
+    private SharedConnectivitySettingsState(boolean instantTetherEnabled,
+            PendingIntent pendingIntent, @NonNull Bundle extras) {
         mInstantTetherEnabled = instantTetherEnabled;
+        mInstantTetherSettingsPendingIntent = pendingIntent;
         mExtras = extras;
     }
 
@@ -96,6 +128,16 @@
     }
 
     /**
+     * Gets the pending intent to open Instant Tether settings page.
+     *
+     * @return Returns the pending intent that opens the settings page, null if none.
+     */
+    @Nullable
+    public PendingIntent getInstantTetherSettingsPendingIntent() {
+        return mInstantTetherSettingsPendingIntent;
+    }
+
+    /**
      * Gets the extras Bundle.
      *
      * @return Returns a Bundle object.
@@ -109,12 +151,14 @@
     public boolean equals(Object obj) {
         if (!(obj instanceof SharedConnectivitySettingsState)) return false;
         SharedConnectivitySettingsState other = (SharedConnectivitySettingsState) obj;
-        return mInstantTetherEnabled == other.isInstantTetherEnabled();
+        return mInstantTetherEnabled == other.isInstantTetherEnabled()
+                && Objects.equals(mInstantTetherSettingsPendingIntent,
+                other.getInstantTetherSettingsPendingIntent());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mInstantTetherEnabled);
+        return Objects.hash(mInstantTetherEnabled, mInstantTetherSettingsPendingIntent);
     }
 
     @Override
@@ -124,6 +168,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mInstantTetherSettingsPendingIntent.writeToParcel(dest, 0);
         dest.writeBoolean(mInstantTetherEnabled);
         dest.writeBundle(mExtras);
     }
@@ -135,8 +180,10 @@
      */
     @NonNull
     public static SharedConnectivitySettingsState readFromParcel(@NonNull Parcel in) {
-        return new SharedConnectivitySettingsState(in.readBoolean(),
-                in.readBundle());
+        PendingIntent pendingIntent = PendingIntent.CREATOR.createFromParcel(in);
+        boolean instantTetherEnabled = in.readBoolean();
+        Bundle extras = in.readBundle();
+        return new SharedConnectivitySettingsState(instantTetherEnabled, pendingIntent, extras);
     }
 
     @NonNull
@@ -156,6 +203,7 @@
     public String toString() {
         return new StringBuilder("SharedConnectivitySettingsState[")
                 .append("instantTetherEnabled=").append(mInstantTetherEnabled)
+                .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent.toString())
                 .append("extras=").append(mExtras.toString())
                 .append("]").toString();
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index 57108e4..87ca99f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -68,9 +68,7 @@
             new RemoteCallbackList<>();
     private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList();
     private List<KnownNetwork> mKnownNetworks = Collections.emptyList();
-    private SharedConnectivitySettingsState mSettingsState =
-            new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(false)
-                    .setExtras(Bundle.EMPTY).build();
+    private SharedConnectivitySettingsState mSettingsState = null;
     private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus =
             new HotspotNetworkConnectionStatus.Builder()
                     .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
@@ -202,6 +200,13 @@
             @Override
             public SharedConnectivitySettingsState getSettingsState() {
                 checkPermissions();
+                // Done lazily since creating it needs a context.
+                if (mSettingsState == null) {
+                    mSettingsState = new SharedConnectivitySettingsState
+                            .Builder(getApplicationContext())
+                            .setInstantTetherEnabled(false)
+                            .setExtras(Bundle.EMPTY).build();
+                }
                 return mSettingsState;
             }
 
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
index c9105f7..7a29969 100644
--- a/wifi/tests/Android.bp
+++ b/wifi/tests/Android.bp
@@ -36,6 +36,7 @@
 
     static_libs: [
         "androidx.test.rules",
+        "androidx.test.core",
         "frameworks-base-testutils",
         "guava",
         "mockito-target-minus-junit4",
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java
index 8302094..0827ffa 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java
@@ -28,6 +28,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.os.Bundle;
 import android.os.Parcel;
 import android.util.ArraySet;
 
@@ -55,6 +56,8 @@
     private static final String HOTSPOT_SSID = "TEST_SSID";
     private static final String HOTSPOT_BSSID = "TEST _BSSID";
     private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
+    private static final String BUNDLE_KEY = "INT-KEY";
+    private static final int BUNDLE_VALUE = 1;
 
     private static final long DEVICE_ID_1 = 111L;
     private static final NetworkProviderInfo NETWORK_PROVIDER_INFO1 =
@@ -138,6 +141,7 @@
         assertThat(network.getHotspotSsid()).isEqualTo(HOTSPOT_SSID);
         assertThat(network.getHotspotBssid()).isEqualTo(HOTSPOT_BSSID);
         assertThat(network.getHotspotSecurityTypes()).containsExactlyElementsIn(securityTypes);
+        assertThat(network.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
     }
 
     @Test
@@ -161,11 +165,18 @@
                 .setHostNetworkType(NETWORK_TYPE)
                 .setNetworkName(NETWORK_NAME)
                 .setHotspotSsid(HOTSPOT_SSID)
-                .setHotspotBssid(HOTSPOT_BSSID);
+                .setHotspotBssid(HOTSPOT_BSSID)
+                .setExtras(buildBundle());
         Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType);
         if (withNetworkProviderInfo) {
             builder.setNetworkProviderInfo(NETWORK_PROVIDER_INFO);
         }
         return builder;
     }
+
+    private Bundle buildBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
+        return bundle;
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
index 1ecba76..81d7b44 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
@@ -25,6 +25,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.os.Bundle;
 import android.os.Parcel;
 import android.util.ArraySet;
 
@@ -47,6 +48,9 @@
             new NetworkProviderInfo.Builder("TEST_NAME", "TEST_MODEL")
                     .setDeviceType(DEVICE_TYPE_TABLET).setConnectionStrength(2)
                     .setBatteryPercentage(50).build();
+    private static final String BUNDLE_KEY = "INT-KEY";
+    private static final int BUNDLE_VALUE = 1;
+
     private static final int NETWORK_SOURCE_1 = NETWORK_SOURCE_CLOUD_SELF;
     private static final String SSID_1 = "TEST_SSID1";
     private static final int[] SECURITY_TYPES_1 = {SECURITY_TYPE_PSK};
@@ -113,6 +117,7 @@
         assertThat(network.getSsid()).isEqualTo(SSID);
         assertThat(network.getSecurityTypes()).containsExactlyElementsIn(securityTypes);
         assertThat(network.getNetworkProviderInfo()).isEqualTo(NETWORK_PROVIDER_INFO);
+        assertThat(network.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
     }
 
     @Test
@@ -125,8 +130,15 @@
 
     private KnownNetwork.Builder buildKnownNetworkBuilder() {
         KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE)
-                .setSsid(SSID).setNetworkProviderInfo(NETWORK_PROVIDER_INFO);
+                .setSsid(SSID).setNetworkProviderInfo(NETWORK_PROVIDER_INFO)
+                .setExtras(buildBundle());
         Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType);
         return builder;
     }
+
+    private Bundle buildBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
+        return bundle;
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java
index 8f35d8d..4aa9ca6 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java
@@ -21,6 +21,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.os.Bundle;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -38,6 +39,8 @@
     private static final String DEVICE_MODEL = "TEST_MODEL";
     private static final int BATTERY_PERCENTAGE = 50;
     private static final int CONNECTION_STRENGTH = 2;
+    private static final String BUNDLE_KEY = "INT-KEY";
+    private static final int BUNDLE_VALUE = 1;
 
     private static final int DEVICE_TYPE_1 = DEVICE_TYPE_LAPTOP;
     private static final String DEVICE_NAME_1 = "TEST_NAME1";
@@ -105,6 +108,7 @@
         assertThat(info.getModelName()).isEqualTo(DEVICE_MODEL);
         assertThat(info.getBatteryPercentage()).isEqualTo(BATTERY_PERCENTAGE);
         assertThat(info.getConnectionStrength()).isEqualTo(CONNECTION_STRENGTH);
+        assertThat(info.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
     }
 
     @Test
@@ -118,6 +122,13 @@
     private NetworkProviderInfo.Builder buildNetworkProviderInfoBuilder() {
         return new NetworkProviderInfo.Builder(DEVICE_NAME, DEVICE_MODEL).setDeviceType(DEVICE_TYPE)
                 .setBatteryPercentage(BATTERY_PERCENTAGE)
-                .setConnectionStrength(CONNECTION_STRENGTH);
+                .setConnectionStrength(CONNECTION_STRENGTH)
+                .setExtras(buildBundle());
+    }
+
+    private Bundle buildBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
+        return bundle;
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index 7578dfd..71239087 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -497,8 +497,9 @@
     @Test
     public void getSettingsState_serviceConnected_shouldReturnState() throws RemoteException {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
-        SharedConnectivitySettingsState state = new SharedConnectivitySettingsState.Builder()
-                .setInstantTetherEnabled(true).setExtras(new Bundle()).build();
+        SharedConnectivitySettingsState state =
+                new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true)
+                        .setExtras(new Bundle()).build();
         manager.setService(mService);
         when(mService.getSettingsState()).thenReturn(state);
 
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
index 752b749..5e17dfb 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
@@ -18,8 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.Intent;
 import android.os.Parcel;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -30,8 +32,12 @@
 @SmallTest
 public class SharedConnectivitySettingsStateTest {
     private static final boolean INSTANT_TETHER_STATE = true;
+    private static final String INTENT_ACTION = "instant.tether.settings";
 
     private static final boolean INSTANT_TETHER_STATE_1 = false;
+    private static final String INTENT_ACTION_1 = "instant.tether.settings1";
+
+
     /**
      * Verifies parcel serialization/deserialization.
      */
@@ -39,16 +45,11 @@
     public void testParcelOperation() {
         SharedConnectivitySettingsState state = buildSettingsStateBuilder().build();
 
-        Parcel parcelW = Parcel.obtain();
-        state.writeToParcel(parcelW, 0);
-        byte[] bytes = parcelW.marshall();
-        parcelW.recycle();
-
-        Parcel parcelR = Parcel.obtain();
-        parcelR.unmarshall(bytes, 0, bytes.length);
-        parcelR.setDataPosition(0);
+        Parcel parcel = Parcel.obtain();
+        state.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
         SharedConnectivitySettingsState fromParcel =
-                SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR);
+                SharedConnectivitySettingsState.CREATOR.createFromParcel(parcel);
 
         assertThat(fromParcel).isEqualTo(state);
         assertThat(fromParcel.hashCode()).isEqualTo(state.hashCode());
@@ -66,6 +67,10 @@
         SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder()
                 .setInstantTetherEnabled(INSTANT_TETHER_STATE_1);
         assertThat(builder.build()).isNotEqualTo(state1);
+
+        builder = buildSettingsStateBuilder()
+                .setInstantTetherSettingsPendingIntent(new Intent(INTENT_ACTION_1));
+        assertThat(builder.build()).isNotEqualTo(state1);
     }
 
     /**
@@ -86,7 +91,9 @@
     }
 
     private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() {
-        return new SharedConnectivitySettingsState.Builder()
-                .setInstantTetherEnabled(INSTANT_TETHER_STATE);
+        return new SharedConnectivitySettingsState.Builder(
+                ApplicationProvider.getApplicationContext())
+                .setInstantTetherEnabled(INSTANT_TETHER_STATE)
+                .setInstantTetherSettingsPendingIntent(new Intent(INTENT_ACTION));
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index b8b6b767..514ba3c 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -75,9 +75,6 @@
                     .addSecurityType(SECURITY_TYPE_EAP).setNetworkProviderInfo(
                             NETWORK_PROVIDER_INFO).build();
     private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
-    private static final SharedConnectivitySettingsState SETTINGS_STATE =
-            new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true)
-                    .setExtras(Bundle.EMPTY).build();
     private static final HotspotNetworkConnectionStatus TETHER_NETWORK_CONNECTION_STATUS =
             new HotspotNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN)
                     .setHotspotNetwork(HOTSPOT_NETWORK).setExtras(Bundle.EMPTY).build();
@@ -155,10 +152,11 @@
         SharedConnectivityService service = createService();
         ISharedConnectivityService.Stub binder =
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
+        when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
 
-        service.setSettingsState(SETTINGS_STATE);
+        service.setSettingsState(buildSettingsState());
 
-        assertThat(binder.getSettingsState()).isEqualTo(SETTINGS_STATE);
+        assertThat(binder.getSettingsState()).isEqualTo(buildSettingsState());
     }
 
     @Test
@@ -232,4 +230,10 @@
         service.attachBaseContext(mContext);
         return service;
     }
+
+    private SharedConnectivitySettingsState buildSettingsState() {
+        return new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true)
+                .setInstantTetherSettingsPendingIntent(new Intent())
+                .setExtras(Bundle.EMPTY).build();
+    }
 }